Problem/Motivation

PDO::FETCH_CLASS has two problems:

  1. builds the object before calling the constructor.
  2. builds the object without respecting the visibility modifiers of either the constructor or the properties that are set.

Now, 1. could be fixed by setting FETCH_PROPS_LATE as we did in #298309: DatabaseStatement::execute with PDO::FETCH_CLASS calls __construct() after setting object attributes but that was reverted in #310904: Use early fetch and document why due to a PHP bug which got fixed in a PHP version one too late for Drupal 7 but aplenty for Drupal 8.

Proposed resolution

Revert #310904: Use early fetch and document why .

Remaining tasks

The tests should be already there IMO, so just a simple patch.

User interface changes

None.

API changes

None?

CommentFileSizeAuthor
#23 1854752_23.patch1021 bytesamateescu
#22 1854752_22.patch905 byteschx
#3 fetch-class.patch790 bytesgábor hojtsy

Comments

Letharion’s picture

Priority: Normal » Critical

Info from reyero on IRC:

if you look at the fetchObject() method, if there's $class_name, then it is not stored anywhere
while PDO:FETCH_CLASS is set
so it will break when you actually fetch the object with ->next()
the underlying issue is StatementInterface missing the fetchObject method + StatementPrefetch::fetchObject() being broken
so for our standards, if the method is not in the interface, I think every other code using fetchObject() is a bug

Not being able to enable a core module (locale) on a core supported db-backend (sqlite) sounds like a critical, no?

gábor hojtsy’s picture

Title: Cannot enable locale module on sqlite » DBTNG StatementPrefetch fetchObject implementation broken

So the problem is that

- sqlite needs to use StatementPrefetch instead of PDOStatement (see comments in the sqlite library)
- StatementPrefetch is custom implemented in our codebase
- fetchObject() in StatementPrefetch takes a $class_name and then makes decisions based on that class name but then never passes that on (so the actual fetching would happen using that $class_name);
- there are no docs on fetchObject() and it is not a method defined in StatementInterface either (so it is hard to tell how people intended this to work)

So a minimal fix at least is to include the class name with the fetch options like current() expects it to be, which makes this fetch into the right object. This does not fix the underlying problem of no documentation and implementation of a method that is not on the interface but is in the pattern of other fetch methods (which are defined on the interface).

The intention of fetchObject() looking at http://php.net/manual/en/pdostatement.fetchobject.php is to "return an instance of the required class with property names that correspond to the column names", so passing on the class name to the fetcher where the column data is filled in is minimally required.

gábor hojtsy’s picture

Status: Active » Needs review
StatusFileSize
new790 bytes

Oh, the minimal patch.

gábor hojtsy’s picture

Title: DBTNG StatementPrefetch fetchObject implementation broken » DBTNG StatementPrefetch fetchObject implementation broken, locale whitescreens
Component: locale.module » database system

Also looked at whether I can update StatementInterface for a more complete fix but various interface elements are commented out in there: http://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%2..., so if we look at the interface, fetching objects is not possible (both fetchObject() and setFetchMode() are commented out, both of which would be required to be able to fetch an object).

FetchObject() is used numerous times in core, but only locale module attempts to use it according to the PDO interface (which the StatementPrefetch method also exposed, but the interface does not even attempt to define). So with this you cannot make an sqlite based Drupa 8 installation that uses locale module.

gábor hojtsy’s picture

Also, we could write tests for this but it would never fail on the testbot, because the testbot does not run sqlite. Is someone running our tests on sqlite?

damien tournoud’s picture

Why are we using PDO::FETCH_CLASS in the first place? This is a *very* broken feature of PDO that we should stay as far from it as we can.

Some examples of why it is broken: (1) PDO::FETCH_CLASS builds the object *without calling the constructor*, and (2) PDO::FETCH_CLASS builds the object without respecting the visibility modifiers of either the constructor or the properties that are set.

Being implemented in pure PHP, StatementPrefetch cannot reproduce the brokeness of PDO::FETCH_CLASS in all its glory. In particular, we cannot instantiate an object without calling its constructor before PHP 5.4.

I suggest refactoring the calling code from:

    $string = $this->dbStringSelect($conditions)
      ->execute()
      ->fetchObject('Drupal\locale\SourceString');

to something like:

    $values = $this->dbStringSelect($conditions)
      ->execute()
      ->fetchAssoc();

    $string = new SourceString($values);
gábor hojtsy’s picture

Title: DBTNG StatementPrefetch fetchObject implementation broken, locale whitescreens » DBTNG StatementPrefetch fetchObject implementation does not support $class_name
Issue tags: -D8MI, -language-ui

@Damien: that sounds interesting, because there are clearly attempts at implementing fetchObject() with a classname in the code, they just don't work at all. They don't reproduce the same brokenness, they just don't work at all.

So I guess we should repurpose this issue to throw an exception from fetchObject() if a class name was provided (both in StatementPrefetch and our extension of Statement)?

We can definitely do like you suggested, but then it is not anymore relevant for this issue, I'll open a new one.

gábor hojtsy’s picture

Priority: Critical » Major

Submitted #1854838: Locale should not use fetchObject with a classname, WSOD on sqlite as a critical locale module issue then. Demoting this to major since the exposed API does not work at all. It does not document it would not work and it does not throw exceptions that it will not work that way.

damien tournoud’s picture

Title: DBTNG StatementPrefetch fetchObject implementation does not support $class_name » Stop pretending that we implement PDO::FETCH_CLASS in StatementPrefetch

I agree.

jose reyero’s picture

Agree with all of the above and on top of that we should properly document the Query::select() method,
which btw returns StatementInterface|PDOStatement (depending on the driver used)

Crell’s picture

Fetching into a class is a supported feature. We have unit tests for it:

http://drupalcode.org/project/drupal.git/blob/refs/heads/8.x:/core/modul...

The pre/post constructor problem is also known, and there is handling for that in the code somewhere. (You can toggle it in the driver.)

Since that test is still passing on a regular basis, I'm unclear what the problem is here.

gábor hojtsy’s picture

@Crell: I don't see a test where it invoked fetchObject() with a class name. The method has a $class_name argument to specify where to fetch the data, but never makes it actually to the place (current() method) where it fetches the data. Maybe other way of fetching to classes work and have tests, but definitely not this method. Also, it is not part of the interface definition and is not implemented to match the PDO interface of the method.

damien tournoud’s picture

PHP 5.2 supports the PDO::FETCH_PROPS_LATE constant, that runs the constructor before force-feeding the object with the properties from the database. This is not the default behavior, and there is no way for us to emulate the default behavior in plain PHP.

Crell’s picture

@Gabor: Ah, I see the distinction. Then we need a test for that as well.

@Damien: I don't follow. We're not using the default behavior, are we?

Oh, crap. Looking back over the history, we are:

#298309: DatabaseStatement::execute with PDO::FETCH_CLASS calls __construct() after setting object attributes
#310904: Use early fetch and document why
http://drupal.org/node/315092

Damien: So there's no way to do pre-constructor fetch in a custom statement class? Since we're no longer supporting 5.2 does that give us more options, since there appear to be version-dependency bugs in the previous issues?

I'd hate to drop fetch class entirely...

damien tournoud’s picture

@Crell, I would support trying to force PDO::FETCH_PROPS_LATE again (in both Statement and StatementPrefetch), as #298309: DatabaseStatement::execute with PDO::FETCH_CLASS calls __construct() after setting object attributes attempted.

Crell’s picture

Me too. I don't have any bandwidth to work on it, though.

catch’s picture

chx’s picture

Status: Closed (duplicate) » Needs review

That's not a duplicate, that's just fetchAll, FETCH_CLASS should not be used at all, I think.

chx’s picture

Title: Stop pretending that we implement PDO::FETCH_CLASS in StatementPrefetch » Do not use or even support PDO::FETCH_CLASS

Better issue title. For reasons outlined in #6 we should not be supporting this... feature.

chx’s picture

Oh and I forgot to tag this.

catch’s picture

Title: Do not use or even support PDO::FETCH_CLASS » Stop pretending that we implement PDO::FETCH_CLASS in StatementPrefetch
chx’s picture

Issue summary: View changes

Updated issue summary.

chx’s picture

Issue summary: View changes

Updated issue summary.

chx’s picture

Title: Stop pretending that we implement PDO::FETCH_CLASS in StatementPrefetch » Re-Add PDO::FETCH_PROPS_LATE to PDO::FETCH_CLASS
StatusFileSize
new905 bytes
amateescu’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new1021 bytes

Since this is basically a revert of #310904: Use early fetch and document why, I think it's fair to bring the comment back too. Other than that, RTBC since we now require a PHP version in which that bug is fixed.

dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to 8.x. Thanks.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

Anonymous’s picture

Issue summary: View changes

Updated issue summary.