PDO 返回空属性名称

Posted

技术标签:

【中文标题】PDO 返回空属性名称【英文标题】:PDO returning empty property name 【发布时间】:2014-05-02 02:15:40 【问题描述】:

我在使用 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 是使用 tdata.so 驱动程序的 Teradata 数据源,由 tdodbc 包提供。

我相信这是因为当 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),它就可以工作!这些 fetch 方法有何不同之处,以至于它们中的一些有时可以访问字段名称,而另一些则不能?

ODBC 跟踪的并排比较(“工作”比较“不工作”)显示没有差异(除了不同的指针地址)。 PDO::FETCH_BOUND 适用于编号列和命名列。 PDO::FETCH_INTO 具有 RoleName 属性的对象没有。

【问题讨论】:

尝试先升级PHP。 我试过 5.5.9。没有变化。 我可以建议两件事:您的SELECT rolename 不区分大小写是吗? PDO 可能会对此感到有些好笑。此外,如果您加入具有相同列的表,则该列可能根本不会返回,除非您在 SQL 中选择 dup 作为 dup1。还有一个最后的建议是尝试反引号'`'列名.. 我尝试了不同的情况,甚至尝试设置 PDO::ATTR_CASE 来强制执行不同的行为,但没有任何变化。这是一个没有连接的简单选择,因此没有重复的列;别名列不会改变任何东西。我没有尝试过反引号,但这些不适用于 Teradata。我使用了双引号,这似乎是Teradata中引用保留字的正确方法,并且没有任何变化。谢谢! 【参考方案1】:

您的问题描述了两个问题:

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

    如 PHP Internals Book 中的 Internal structures and implmentation 中所述,“dynamic properties”(即对象的成员变量未在其类定义中声明,而是在运行时创建)实现为哈希表。

    如果希望使用当前保存在哈希表中的一堆属性填充新实例化的标准对象,可以简单地将对象的 properties 变量指向该现有哈希表 - PHP 的 object_and_properties_init() 函数,是 called by odbc_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 first 和its properties are populated later。因此,奇怪的属性名称(例如空字符串)会导致观察到的失败。

    您尝试过的其他 fetch 样式没有遇到同样的问题,因为:

    PDO::FETCH_BOUND 获取由先前调用 PDOStatement::bindColumn() 指定的变量,因此 PHP 永远不会尝试写入具有空名称的属性;

    PDO::FETCH_LAZY skips the whole shebang 和上面的odbc_fetch_object() 类似。

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

    为什么 PDO 认为这个 ODBC 记录集中的列名是空字符串?

    这个问题的答案对我来说不太明显。

    我们之前看到,为了填充属性,PDO 使用stmt->columns[i].name 作为属性名称。这应该在an earlier point 正确填写,而pdo_stmt_describe_columns() was called。这个函数又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(不起作用)使用相同的驱动程序吗?

    特别是Extension Level Functions下的Teradata文档:

    默认情况下,SQLDescribeCol 和 SQLColAttribute 返回列名而不是 Teradata 列标题。如果应用程序希望 ODBC Driver for Teradata 返回列标题而不是实际列名,则 Teradata ODBC 驱动程序选项 对话框中的选项 Use Column Names 不得为使用的 DSN 选择,或在 UNIX 操作系统上设置 DontUseTitles = No。

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

【讨论】:

感谢您提供冗长而翔实的回答。是的,我确定我在两个测试中都使用了相同的驱动程序。如果 stmt->columns[i].name 为空,PDO::FETCH_NAMED 是如何工作的? @mwp:呃,这是一个很好的观点(我完全忽略了这一点)。我能看到的最明显的区别是PDO::FETCH_NAMED 使用stmt->columns[i].name alone,而PDO::FETCH_OBJ 也使用uses stmt->columns[i].namelennamelen 的值来自 call 到 Teradata 的 SQLDescribeCol() 函数......也许它错误地返回 0? 不错!我会调查的。 (或者更恰当地说,请我以前的同事调查一下,因为我不再在那里工作了。)再次感谢您的帮助。【参考方案2】:

我认为现在的“解决方案”是使用获取样式 PDO::FETCH_NAMED 然后转换返回的数组并填充动态类:

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

   return $obj;

【讨论】:

我最初的问题是:“但是,这不适用于在幕后直接使用 PDO::FETCH_CLASS 的某些框架和 ORM。”

以上是关于PDO 返回空属性名称的主要内容,如果未能解决你的问题,请参考以下文章

Javascript 到 PHP PDO 到 MySQL 查询返回空

PDO 返回空数组作为结果

PDO->fetchAll() 在远程服务器上返回空数组

PDO fetchAll 返回空数组,但是可以显示count数量?

MySQL 返回 PDO 占位符名称

MySQL 返回 PDO 占位符名称