Problem/Motivation

Database select query throws an error when trying to fetch all objects as instances of certain class.

The issue is with the column index being set to zero by default in StatementBase class default fetch options definition. That would later on flow to fetchAll method of the PdoResult class and clientFetchAll method of the PdoTrait class.

Code has checks for NULL values and the default column index of zero passes those checks, resulting in class name settings being disregarded.

Steps to reproduce

Create select query, set fetch option with custom class and call fetchAll on the result.

Proposed resolution

Change the setFetchMode method in the StatementBase class to explicitly set the column index to NULL if the fetch mode is set to ClassObject.

  /**
   * {@inheritdoc}
   */
  public function setFetchMode($mode, $a1 = NULL, $a2 = []) {
    if (is_int($mode)) {
      @trigger_error("Passing the \$mode argument as an integer to setFetchMode() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \Drupal\Core\Database\Statement\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
      $mode = $this->pdoToFetchAs($mode);
    }
    assert($mode instanceof FetchAs);

    $this->fetchMode = $mode;
    switch ($mode) {
      case FetchAs::ClassObject:
        $this->fetchOptions['class'] = $a1;
        // NB! Explicitly set the column index to NULL so that fetching all results would not throw an 
        $this->fetchOptions['column'] = NULL;
        if ($a2) {
          $this->fetchOptions['constructor_args'] = $a2;
        }
        break;

      case FetchAs::Column:
        $this->fetchOptions['column'] = $a1;
        break;

    }

    // If the result object is missing, just do with the properties setting.
    try {
      if ($this->result) {
        return $this->result->setFetchMode($mode, $this->fetchOptions);
      }
      return TRUE;
    }
    catch (\LogicException) {
      return TRUE;
    }
  }

Remaining tasks

Review the latest improvements to issue title and description, pull request and possibly accept the merge request.

User interface changes

None.

Introduced terminology

None.

API changes

None.

Data model changes

None.

Release notes snippet

None.

CommentFileSizeAuthor
#22 3552669-22.patch2.66 KBrhezios

Issue fork drupal-3552669

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

pjotr.savitski created an issue. See original summary.

pjotr.savitski’s picture

Issue summary: View changes
quietone’s picture

Version: 11.2.x-dev » 11.x-dev

Hi, in Drupal core changes are made on on 11.x (our main development branch) first, and are then back ported as needed according to the Core change policies. Thanks.

pjotr.savitski’s picture

Version: 11.x-dev » 11.2.x-dev

@quietone Didn't know that. Thank you for the information.

pjotr.savitski’s picture

Version: 11.2.x-dev » 11.x-dev
pjotr.savitski’s picture

A possible solution is to modify the setFetchMode method in the StatementBase class to either remove the column identifier that is initially set to zero in the $fetchOptions parameter. This will allow the clientFetchAll in the PdoTrait class to be passed the class name instead of the column identifier.

The only way for the class name to be used instead of the column identifier is to make sure that column identifier is NULL or missing the key is missing from the array.

pjotr.savitski’s picture

Issue summary: View changes
pjotr.savitski’s picture

Issue summary: View changes
pjotr.savitski’s picture

Issue summary: View changes
pjotr.savitski’s picture

Hope that having the test and possible fix in the issue fork will expedite the process of getting the fix into the released version a bit.

godotislate’s picture

@pjotr.savitski: Create a merge request if you have a fix and test ready in the branch. Then, depending on whether CI tests fail, you may need to rework your changes until all tests pass. Once that's done, change the status of this issue to "Needs review" and eventually someone in the community will take a look at it.

pjotr.savitski’s picture

Status: Active » Needs review
pjotr.savitski’s picture

@godotislate Thank you for help and guidance.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Bug Smash Initiative

Left some comments on the MR thanks!

pjotr.savitski’s picture

Assigned: Unassigned » pjotr.savitski
Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Feedback appears to be addressed.

godotislate’s picture

Ran the test-only job and it confirms the error in the issue title:

There was 1 error:
1) Drupal\KernelTests\Core\Database\FetchTest::testQueryFetchAllAsClassInstances
TypeError: PDOStatement::fetchAll(): Argument #2 must be of type string, int given
/builds/issue/drupal-3552669/core/lib/Drupal/Core/Database/Statement/PdoTrait.php:191
/builds/issue/drupal-3552669/core/lib/Drupal/Core/Database/Statement/PdoResult.php:83
/builds/issue/drupal-3552669/core/lib/Drupal/Core/Database/Statement/StatementBase.php:339
/builds/issue/drupal-3552669/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php:183
quietone’s picture

Assigned: pjotr.savitski » Unassigned
Issue summary: View changes
Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update, +Needs title update

Going through a checklist of things for RTBC issues.

Thanks for the test!

Un-assigning per Assigning ownership of a Drupal core issue.

The proposed resolution in the issue summary has two options and says that the first is preferred. But the MR implements the second option. That should be explained in the issue summary to help reviewers or anyone researching this change.

The title here is an error statement but is should be a description of what is being fixed or improved. The title is used as the git commit message so it should be meaningful and concise. See List of issue fields. Since I can't think of a title right now I am tagging for that and setting to NW. Something about correctly handling the default column of '0'?

rhezios made their first commit to this issue’s fork.

rhezios’s picture

StatusFileSize
new2.66 KB

Merge request !14207 contains the first option by setting the column to NULL. The second option doesn't work with setFetchMode containing constructor args.

pjotr.savitski’s picture

Title: TypeError: PDOStatement::fetchAll(): Argument #2 must be of type string, int given in PDOStatement->fetchAll() » Incorrect handling of fetching all query results as class instances

pjotr.savitski’s picture

Title: Incorrect handling of fetching all query results as class instances » Error when fetching all query results as class instances
pjotr.savitski’s picture

pjotr.savitski’s picture

Issue summary: View changes
pjotr.savitski’s picture

  • Changed the title and summary to be more concise and easy to grasp. Removed additional tags.
  • Removed mention of second possible approach that was initially implemented and submitted with merge request. Closed that merge request.
  • Rebased the branch implementing the initially suggested solution.
pjotr.savitski’s picture

Status: Needs work » Needs review

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

godotislate’s picture

Status: Needs review » Reviewed & tested by the community

Re-based the MR, since it failed on the automated main re-target.

Tests pass.
Test only fails as expected: https://git.drupalcode.org/issue/drupal-3552669/-/jobs/8640046
IS and title look to have been updated per #19.

lgtm

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

I think we should move the fix to \Drupal\Core\Database\Statement\PdoResult::fetchAll as that's were this problem is coming from.

Something like:

  /**
   * {@inheritdoc}
   */
  public function fetchAll(FetchAs $mode, array $fetchOptions): array {
    $columnOrClass = $mode === FetchAs::ClassObject ? $fetchOptions['class'] ?? NULL : $fetchOptions['column'] ?? NULL;
    return $this->clientFetchAll($mode, $columnOrClass, $fetchOptions['constructor_args'] ?? NULL);
  }

Fixing it here would fix this for all calls to the public fetchAll().

dpi made their first commit to this issue’s fork.

dpi’s picture

Status: Needs work » Needs review

Entity Hierarchy ran into this issue in https://git.drupalcode.org/project/entity_hierarchy/-/work_items/3590987, as it passed a class name to fetchAll($column_index), which broke in #3522561: Fully type StatementInterface methods' parameters. So we had to adapt to setFetchMode, which is how I ran into this new issue.

I adapted safety per @alexpott.
I wanted to improve a little typehinting but Coder is unhappy (despite PHPStan accepting it), so whatever.
Merged with todays main branch.

mondrake’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to me. Two tests added to prevent regressions.

dpi’s picture

Status: Reviewed & tested by the community » Needs review

Slight rework for @mondrake feedback.

Use the same assertEquals assertion strategy for both class properties.

mondrake’s picture

Status: Needs review » Reviewed & tested by the community

Thanks

quietone’s picture

I've read the comments and updated credit (which does include me for earlier triage). I didn't find anything unanswered here.

alexpott’s picture

Version: main » 11.4.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed eeb5c183295 to main and 7c0abbbf045 to 11.x and 9282211f1a8 to 11.4.x. Thanks!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • alexpott committed 9282211f on 11.4.x
    fix: #3552669 Error when fetching all query results as class instances...

  • alexpott committed 7c0abbbf on 11.x
    fix: #3552669 Error when fetching all query results as class instances...

  • alexpott committed eeb5c183 on main
    fix: #3552669 Error when fetching all query results as class instances...