Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PDO returning empty property name

Tags:

I'm having a strange issue with pdo_odbc and PDO::FETCH_OBJ (and PDO::FETCH_CLASS) that results in the following error message:

PHP Fatal error:  Cannot access empty property 

Here's the code:

$dbh = new PDO("odbc:FOO");  $sth = $dbh->query("   SELECT rolename   FROM dbc.allrolerights   WHERE databasename = 'BAR' ");  $result = $sth->fetch(PDO::FETCH_OBJ); 

The FOO DSN, for reference, is a Teradata datasource using the tdata.so driver, provided by the tdodbc package.

I believe this is happening because the field name (as returned from the ODBC query) is blank when PDO calls zend_API.h:object_init_ex() to instantiate the stdClass object. If I switch to PDO::FETCH_LAZY and var_dump() the row, I get the following:

object(PDORow)#3 (2) {   ["queryString"]=>   string(95) "   SELECT rolename   FROM dbc.allrolerights   WHERE databasename = 'BAR' "   [""]=>   string(30) "FNAR                          " } 

Which seems to back it up. I've tried several different PDO attribute combinations and bunch of different angles to work around the problem. One solution is to fetch an associative array and pass it to the class constructor. However, this doesn't work for certain frameworks and ORMs that are using PDO::FETCH_CLASS directly, behind the scenes.

I want to add that other fetch methods seem to do the right thing, for example, PDO::FETCH_NAMED:

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

I'm looking for something I can put in the PDO dbh or sth definition, or in the odbc.ini or odbcinst.ini for the datasource or driver, to resolve this issue. Thank you in advance.

Update: odbc_fetch_object() (i.e. not PDO) works great with the same exact everything. Just wanted to mention it. There clearly doesn't seem to be any serious issues with PHP, unixODBC, or the ODBC driver. It's something in the PDO code. Time to open a bug report... 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); 

And the output:

object(stdClass)#1 (1) {   ["RoleName"]=>   string(30) "FNAR                          " } 

Update 2: The situation continues to grow more and more bizarre. I can do a PDO::FETCH_LAZY and see the blank column name as seen in the var_dump() above, but if I try to access the property by name (e.g. $result->RoleName), it works! What are these fetch methods doing so differently that some of them can sometimes access the field names, and others cannot?

Side-by-side comparisons of ODBC traces ("working" cf. "not working") shows no differences (other than different pointer addresses). PDO::FETCH_BOUND works with both numbered and named columns. PDO::FETCH_INTO an object with a RoleName property does not.

like image 356
mwp Avatar asked Mar 24 '14 15:03

mwp


People also ask

What does PDO fetchAll return?

Return Values ¶ PDOStatement::fetchAll() returns an array containing all of the remaining rows in the result set. The array represents each row as either an array of column values or an object with properties corresponding to each column name. An empty array is returned if there are zero results to fetch.

How to use fetchAll in PHP?

The fetch_all() / mysqli_fetch_all() function fetches all result rows and returns the result-set as an associative array, a numeric array, or both. Note: This function is available only with MySQL Native Driver.


1 Answers

Your question describes two problems:

  1. Why is it that objects cannot be created with properties having empty-string names when using PDO::FETCH_OBJ, but apparently can when using other methods?

    As documented under Internal structures and implmentation in the PHP Internals Book, "dynamic properties" (i.e. an object's member variables that are not declared in its class definition, but rather are created at runtime) are implemented as a hash table.

    If one wishes to populate a newly instantiated standard object with a bunch of properties that are currently held in a hash table, one can simply point the object's properties variable at that existing hash table—PHP's object_and_properties_init() function, which is called by odbc_fetch_object(), does exactly that without performing any sanity checking on the table's keys. Consequently, one can instantiate an object with strange property names (such as the empty string).

    On the other hand, if one already has an instantiated object and needs to set a property value (whilst preserving any others that already exist), one must copy that value into the object's hash table—PHP's zend_std_write_property() method, which handles this action for standard objects, does exactly that having first performed a sanity check on the property name. Consequently, one cannot add a property with a strange name (such as the empty string) to an existing object.

    This discrepancy in sanity-checking between the two approaches appears, to my mind, to be a bug: any restrictions on dynamic property names should be enforced irrespective of the method by which such properties are created. Whether strange names of this sort should be allowed (and therefore the sanity checking should be removed from the latter method), or disallowed (and therefore some sanity checking should be added to the former method), is a decision that I shall leave to the PHP developers.

    How does PDO fit into all this?

    PDOStatement::fetch() first prepares the destination into which results will be stored and then iterates over the columns storing each field in turn: I imagine it does this in order to simplify the codebase, as each fetch style can be implemented within the same structure. However, this does mean that when called using the PDO::FETCH_OBJ style (and also both PDO::FETCH_CLASS and PDO::FETCH_INTO, as you have observed), an object is instantiated first and its properties are populated later. Consequently, strange property names (such as the empty string) result in the observed failure.

    The other fetch styles that you have tried don't experience the same problem because:

    • PDO::FETCH_BOUND fetches into variables that were specified by a previous call to PDOStatement::bindColumn(), so PHP never attempts to write to a property having an empty name;

    • PDO::FETCH_LAZY skips the whole shebang and does things similar manner to odbc_fetch_object() above.

    Similarly, array-based fetch styles won't suffer similar problems because empty-string keys are perfectly valid in those hash tables.

  2. Why is it that PDO thinks the column names in this ODBC recordset are empty strings?

    The answer to this is much less obvious to me.

    We saw earlier that, to populate properties, PDO uses stmt->columns[i].name as the property name. This should have been correctly filled at an earlier point, when pdo_stmt_describe_columns() was called. This function in turn had called the driver's describer method for each column in the resultset: in the case of PDO_ODBC, that's odbc_stmt_describe() which does indeed assign a value to that field.

    So, everything looks fine on the PHP side. It would be interesting to know whether the call to the driver's SQLDescribeCol() function correctly populated the column name into the buffer provided as its third argument: one imagines not, which would suggest that the problem lies in the ODBC driver itself. You mentioned that you're using Teradata: but are you sure that you are using the same driver for both PDO_ODBC (which isn't working) and ext/odbc (which is)?

    In particular, Teradata document under Extension Level Functions:

    By default, SQLDescribeCol and SQLColAttribute return the column name instead of the Teradata column title. If an application wants ODBC Driver for Teradata to return the column title instead of the actual column name, then the option Use Column Names in the Teradata ODBC Driver Options dialog box must not be selected for the DSN used, or set DontUseTitles = No on the UNIX OS.

    Returning the column title instead of the actual column name can cause problems for certain applications, such as Crystal Reports, because they expect to get the column name and not the column title.

like image 61
eggyal Avatar answered Sep 25 '22 09:09

eggyal