Change record status: 
Project: 
Introduced in branch: 
8.0.x
Introduced in version: 
8.0.0-beta4
Description: 

As a security hardening, the automatic expansion of database query placeholders has been removed to require explicit intent by the developer to use an array of values.

A "normal" placeholder like :name will no longer be expanded to multiple placeholder if the value is an array. To support IN() syntax, we instead use a special placeholder format like :name[]. The brackets on the end are intended to evoke array syntax so it's visually clear that this will take an array of values.

In addition, when using db_select() it now requires the explicit passing of 'IN' to the ->condition() method instead of assuming it based on an array value, so that there is never unintended expansion.

Before:


db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN (:vids) AND th.parent = 0', array(':vids' => $vids))->fetchCol();

After:

db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN (:vids[]) AND th.parent = 0', array(':vids[]' => $vids))->fetchCol();

Before (where $tids is always an array):


     $this->database->delete('taxonomy_term_hierarchy')
       ->condition('tid', $tids)

After (make explicit use of an IN condition):

     $this->database->delete('taxonomy_term_hierarchy')
       ->condition('tid', $tids, 'IN')

If your code accepts both array or scalar values for the same query value (not a best practice), you should either use type-hinting to enforce an array, or you can cast to array:

Before:


      // Value can be a string or array of strings.
      $query->condition('l.' . $field, $value);

After:


      // Cast scalars to array so we can consistently use an IN condition.
      $query->condition('l.' . $field, (array) $value, 'IN');

Example of Drupal 7.x to 8.0.x update of db_query() with IN condition:

Drupal 7 from function drupal_lookup_path()


          $cache['system_paths'] = $cached->data;
          // Now fetch the aliases corresponding to these system paths.
          $args = array(
            ':system' => $cache['system_paths'],
            ':language' => $path_language,
            ':language_none' => LANGUAGE_NONE,
          );

          if ($path_language == LANGUAGE_NONE) {
            // Prevent PDO from complaining about a token the query doesn't use.
            unset($args[':language']);
            $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language = :language_none ORDER BY pid ASC', $args);
          }
          elseif ($path_language < LANGUAGE_NONE) {
            $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language ASC, pid ASC', $args);
          }
          else {
            $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language DESC, pid ASC', $args);
          }
          $cache['map'][$path_language] = $result->fetchAllKeyed();
          // Keep a record of paths with no alias to avoid querying twice.
          $cache['no_aliases'][$path_language] = array_flip(array_diff_key($cache['system_paths'], array_keys($cache['map'][$path_language])));
        }

Drupal 8 from method \Drupal\Core\Path\AliasStorage::preloadPathAlias()


  public function preloadPathAlias($preloaded, $langcode) {
    $args = array(
      ':system[]' => $preloaded,
      ':langcode' => $langcode,
      ':langcode_undetermined' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
    );

    if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
      // Prevent PDO from complaining about a token the query doesn't use.
      unset($args[':langcode']);
      $result = $this->connection->query('SELECT source, alias FROM {url_alias} WHERE source IN (:system[]) AND langcode = :langcode_undetermined ORDER BY pid ASC', $args);
    }
    elseif ($langcode < LanguageInterface::LANGCODE_NOT_SPECIFIED) {
      $result = $this->connection->query('SELECT source, alias FROM {url_alias} WHERE source IN (:system[]) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid ASC', $args);
    }
    else {
      $result = $this->connection->query('SELECT source, alias FROM {url_alias} WHERE source IN (:system[]) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid ASC', $args);
    }

    return $result->fetchAllKeyed();
  }

Impacts: 
Module developers