PHP 8.1: Deprecation warnings when executing a query within a where condition in Drupal database object

Created on 5 April 2023, about 2 years ago
Updated 1 May 2023, about 2 years ago

Problem/Motivation

Executing a query within a where condition in the Drupal database connection class with PHP 8.1 generates deprecation warnings:

Deprecated function: stripos(): Passing null to parameter #1 ($haystack) of type string is deprecated en Drupal\Core\Database\Query\Condition->compile()

Deprecated function: strpbrk(): Passing null to parameter #1 ($string) of type string is deprecated en Drupal\Core\Database\Query\Condition->compile()

Deprecated function: strtoupper(): Passing null to parameter #1 ($string) of type string is deprecated en Drupal\Core\Database\Query\Condition->mapConditionOperator()

Steps to reproduce

In a Drupal 9.5.x project with PHP 8.1 and MariaDB 10.4.13 initiate a database connection (\Drupal\Core\Database\Connection) and an unexecuted query. Create another database connection that only contains an unexecuted condition. To the first query, add a where clause with the second query inside. Execute the first query and check the logs.

Example with the database inside a custom class:

/**
 * Database connection service.
 *
 * @var \Drupal\Core\Database\Connection
 */
  protected $database;

/**
 * Constructor.
 *
 * @param \Drupal\Core\Database\Connection $database
 *  Database connection service.
 */
  public function __construct(Connection $database) {
    $this->database = $database;
  }
  $query = $this->database->select('table', 't');
  $query->fields('t', ['column_1', 'column_2']);

  $query2 = $this->database->condition('t.field', 'value', '=');

  $query->where($query2);

  $query->execute()->fetchAll();

Proposed resolution

Force data types passed to functions to be strings.

🐛 Bug report
Status

Closed: works as designed

Version

9.5

Component
Database 

Last updated 1 day ago

  • Maintained by
  • 🇳🇱Netherlands @daffie
Created by

🇪🇸Spain enchufe Spain

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Sign in to follow issues

Comments & Activities

  • Issue created by @enchufe
  • @enchufe opened merge request.
  • Status changed to Needs review about 2 years ago
  • 🇮🇳India sakthi_dev

    Instead of hard coding (casting the variable), suggested another method based on value. Please review.

  • 🇪🇸Spain enchufe Spain

    #3 Your patch seems to be more secure by adding additional checks for variables. Do I manually add it to my MR or is there a way to merge the patch? I'm relatively new to contributing code to Drupal.

  • 🇮🇳India sakthi_dev

    Pushed the changes to the same branch. Please review.

  • 🇪🇸Spain enchufe Spain

    Looks good, the MR works for me

  • Status changed to Needs work about 2 years ago
  • 🇺🇸United States smustgrave

    Tickets like these appear to be covering up a large problem by addressing the symptom.

    Did not verify the issue in the issue summary (but thanks for the steps!)

    This will need a test case to show the problem.

  • 🇫🇷France Chris64 France

    $operator ?? ''
    instead of
    !empty($operator) ? $operator : ''

  • First commit to issue fork.
  • 🇮🇳India sourabhjain

    Fixed the issue mentioned in #8. Please review.

  • 🇫🇷France Chris64 France

    Same kind of things for the other line,

    - if (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE) {
    + if (stripos(!empty($condition['operator']) ? $condition['operator']: '', 'UNION') !== FALSE || strpbrk(!empty($condition['operator']) ? $condition['operator']: '', '[-\'"();') !== FALSE) {

    but in this case rather,
    if (isset($condition['operator']) && (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE)) {

    But there is some thing weird here: did $condition['operator'] may be null? Is it a regular case? Since we are here because $ignore_operator is FALSE, and if !isset($condition['operator']) then $ignore_operator = TRUE, except if ($condition['field'] instanceof ConditionInterface). If then $ignore_operator = $condition['operator'] === '=' && $condition['value'] === NULL, hence FALSE if $condition['operator'] is null. On an other hand, after, $operator = $connection->mapConditionOperator($condition['operator']); $operator += ['operator' => $condition['operator']]; hence null is passed. On an other hand, if $ignore_operator is TRUE then $operator = ['operator' => '', 'use_value' => FALSE];, looking nice. We may think that in all cases if !isset($condition['operator']) then $ignore_operator = TRUE, and we can not be here.

  • 🇮🇳India sourabhjain

    Resolved custom command failed issue in #9

  • 🇫🇷France Chris64 France

    Yes indeed. I've changed it.

    For the other after thinking should be enough,
    if ($condition['operator'] !== NULL && (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE)) {

  • First commit to issue fork.
  • 🇪🇸Spain enchufe Spain

    Wouldn't it be more efficient to use $condition['operator'] ?? '' instead of !empty($condition['operator']) ? $condition['operator'] : ''? The empty function and the ternary operator are two steps, while the null merge operator (??) is a native PHP operator.

  • 🇫🇷France Chris64 France

    @enchufe I am agree that is better. But I think #13 is better. Since !== FALSE means exists, and if so then TRUE. On the other hand, if $condition['operator'] === NULL then it can not exist, and the condition is FALSE. The problem comes from $condition['operator'] === NULL.

    But in the exemple, $query2 = $this->database->condition('t.field', 'value', '=');, the operator should be '='. Then why $condition['operator'] === NULL? This after #11.

    About the test of #7 it should fail without the patch and succeed with the patch.

    Did some tests exist for the function with the problem? The function is compile in core/lib/Drupal/Core/Database/Query/Condition.php. May be in tests/Drupal/Tests/Core/Database/ConditionTest.php. However nothing seems provide for NULL operator. And 'IS NULL' operator is some thing else.

  • 🇪🇸Spain enchufe Spain

    So, the current MR is slightly safer by first checking if the variable is defined and not null before applying PHP functions. In my specific case it works correctly.

  • Status changed to Closed: works as designed about 2 years ago
  • 🇳🇱Netherlands daffie
      $query = $this->database->select('table', 't');
      $query->fields('t', ['column_1', 'column_2']);
    
      $query2 = $this->database->condition('t.field', 'value', '=');
    
      $query->where($query2);
    
      $query->execute()->fetchAll();
    

    The given example from the issue summary fails, because the function where() only accepts string values. In the given example an instance of a Condition object is not a string value.

    It works as designed! Use condition() instead of where().

Production build 0.71.5 2024