Allow multi-line function calls

Created on 2 August 2022, over 3 years ago
Updated 4 June 2023, over 2 years ago

Problem/Motivation

Splitting #1539712: [policy, no patch] Coding standards for breaking function calls and language constructs across lines as suggested #1539712-92: [policy, no patch] Coding standards for breaking function calls and language constructs across lines and agreed at a #3282227: Coding Standards Meeting 2022-06-07 2100 UTC

There is lengthy discussion in that issue that is not duplicated here, it would be a good idea to look at that. The result of that was the following options.

Steps to reproduce

Proposed resolution

Option A from PSR-2 += as at #1539712-68: [policy, no patch] Coding standards for breaking function calls and language constructs across lines and in the Issue summary of that issue.

  1. When calling functions, argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.

    $foo = foo_function(
      $term_1[$lancode][$delta]['value']['tid'],
      $term_2[$lancode][$delta]['value']['tid'],
      t('Translate some message here; good times.'),
      t('Some other message here.'),
      [
        'foo' => 'thing',
        'bar' => 'thing2',
      ],
      $bar
    );
    
  2. Values in an options-style associative array with more than one value SHOULD be wrapped onto multiple lines; e.g.:

    $result = format_string('@foo at some point before had the value of @bar', [
      '@foo' => $foo,
      '@bar' => t('The Bar in @foo', [
        '@foo' => $foo,
      ]),
    ]);
    
  3. A nested call to a callable returning an object with chainable methods SHOULD be placed on the same line, using the regular coding style for chained method calls on the new indentation level; e.g.:

    $value = db_select('users')
      ->fields('users', ['uid'])
      ->condition('uid', $account->uid, '<>')
      ->condition(db_or()
        ->condition('mail', db_like($form_state['values']['mail']), 'LIKE')
        ->condition('name', db_like($form_state['values']['mail']), 'LIKE')
      )
      ->range(0, 1)
      ->execute()
      ->fetchField();
    
  4. Opening and closing parentheses MUST use the same level of indentation across multiple lines.

Option B from from #1539712-68: [policy, no patch] Coding standards for breaking function calls and language constructs across lines and in the Issue summary of that issue.

  1. Function call parameters MAY wrap multiple lines, with each parameter on its own line. This MAY increase code readability when the length of individual paramters is very long, especially when the function call includes other function calls or arrays. For example:
    $foo = foo_function(
      $term_1[$lancode][$delta]['value']['tid'],
      $term_2[$lancode][$delta]['value']['tid'],
      t('Translate some message here; good times.'),
      t('Some other message here.'), [
        'foo' => 'thing',
        'bar' => 'thing2',
      ],
      $bar
    );
    

    Ie. Parameters that contain natural language strings, references to values deeply nested within arrays, inline/"options-style" definitions of arrays or other parameters over 30 characters long are often good candidates to be considered "very long" parameters.

    Developers SHOULD focus on the readability of the logic of the entire code block though, and take into account how common the parameters of the called function are. As an example for the latter, which is passing forward the passed-in arguments only:

      file_field_load($entity_type, $entities, $field, $instances, $langcode, $items, $age);
    

    Function calls MUST either wrap all parameters or none. Do NOT do this:

    $foo = foo_function($term_1[$lancode][$delta]['value']['tid'], $term_2[$lancode][$delta]['value']['tid'], t('Message with %arg1 and @arg2', [
      '%arg1' => $bar,
      '@arg2' => $baz,
    ]));
    
  2. Values in an options-style associative array that contains more than one value SHOULD be wrapped onto multiple lines; e.g.:
    $result = format_string('@foo at some point before had the value of @bar', [
      '@foo' => $foo,
      '@bar' => t('The Bar in @foo', [
        '@foo' => $foo,
      ]),
    ]);
    
  3. Parameters of nested function calls, MAY be wrapped onto multiple lines, and MAY keep the first parameter on the same line; e.g.:
    $this->assertEqual(
      $article_built[$field_name]['#items'][0]['fid'],
      $default_images['field_new']->fid,
      format_plural($count,
        'An existing article node without an image has the expected default image file ID of @fid.',
        '@count existing article nodes without an image have the expected default image file ID of @fid.', [
          '@fid' => $default_images['field_new']->fid
        ]
      )
    );
    
  4. A nested call to a callable returning an object with chainable methods SHOULD be placed on the same line, using the regular coding style for chained method calls on the new indentation level; e.g.:
    $value = db_select('users')
      ->fields('users', ['uid'])
      ->condition('uid', $account->uid, '<>')
      ->condition(db_or()
        ->condition('mail', db_like($form_state['values']['mail']), 'LIKE')
        ->condition('name', db_like($form_state['values']['mail']), 'LIKE')
      )
      ->range(0, 1)
      ->execute()
      ->fetchField();
    
  5. Opening and closing parentheses MUST use the same level of indentation across multiple lines.

Option C from from #1539712-69: [policy, no patch] Coding standards for breaking function calls and language constructs across lines and in the Issue summary of that issue.

As defined above but all multi-line structures, whether nested or not, must have each parameter/element each starting a new, indented line.

It is worth noting that this is essentially equivalent to what PSR and PEAR say about multi-line function calls and what PEAR
says about multi-line if statements.

Original examples:

$foo = foo_function(
  $term_1[$lancode][$delta]['value']['tid'],
  $term_2[$lancode][$delta]['value']['tid'],
  t('Translate some message here; good times.'),
  t('Some other message here.'), [
    'foo' => 'thing',
    'bar' => 'thing2',
  ],
  $bar
);
$this->assertEqual(
  $article_built[$field_name]['#items'][0]['fid'],
  $default_images['field_new']->fid,
  format_plural($count,
    'An existing article node without an image has the expected default image file ID of @fid.',
    '@count existing article nodes without an image have the expected default image file ID of @fid.', [
      '@fid' => $default_images['field_new']->fid
    ]
  )
);
$result = format_string('@foo at some point before had the value of @bar', [
  '@foo' => $foo,
  '@bar' => t('The Bar in @foo', [
    '@foo' => $foo,
  ]),
]);
  if (!isset($file)
      // If no core compatibility version is defined at all, then it is
      // impossible to check for updates.
      || !isset($file->info['core'])
      // If the extension is not compatible with the current core, it cannot
      // be enabled in the first place.
      || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY
      // If the new version requires a higher PHP version than the available,
      // then it is not possible to update to it. The PHP version property
      // defaults to DRUPAL_MINIMUM_PHP for all extensions.
      || version_compare(phpversion(), $file->info['php']) < 0) {
    return TRUE;
  }

Alternative examples:

$foo = foo_function(
  $term_1[$lancode][$delta]['value']['tid'],
  $term_2[$lancode][$delta]['value']['tid'],
  t('Translate some message here; good times.'),
  t('Some other message here.'),
  [
    'foo' => 'thing',
    'bar' => 'thing2',
  ),
  $bar
];
$this->assertEqual(
  $article_built[$field_name]['#items'][0]['fid'],
  $default_images['field_new']->fid,
  format_plural(
    $count,
    'An existing article node without an image has the expected default image file ID of @fid.',
    '@count existing article nodes without an image have the expected default image file ID of @fid.',
    [
      '@fid' => $default_images['field_new']->fid
    ]
  )
);
$result = format_string(
  '@foo at some point before had the value of @bar',
  [
    '@foo' => $foo,
    '@bar' => t(
      'The Bar in @foo',
      [
        '@foo' => $foo,
      ]
    ),
  ]
);

<?php
  if (
      !isset($file)
      // If no core compatibility version is defined at all, then it is
      // impossible to check for updates.
      || !isset($file->info['core'])
      // If the extension is not compatible with the current core, it cannot
      // be enabled in the first place.
      || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY
      // If the new version requires a higher PHP version than the available,
      // then it is not possible to update to it. The PHP version property
      // defaults to DRUPAL_MINIMUM_PHP for all extensions.
      || version_compare(phpversion(), $file->info['php']) < 0
  ) {
    return TRUE;
  }

?>

Remaining tasks

User interface changes

API changes

Data model changes

📌 Task
Status

Active

Component

Drupal Core Standards

Created by

🇳🇿New Zealand quietone

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇦🇹Austria drunken monkey Vienna, Austria

    Option A sound good to me, though I don’t quite see the difference between the three. Is it just in the phrasing? In that case, definitely option A, option B has a number of problems in my opinion. (And I don’t quite understand what the difference is for option C.)

    #5++
    I don’t think the potential parsing errors are a good argument, but the diffs definitely are.
    We’d just have to include the condition that this only applies if the code in question depends on PHP 7.3+ – which Drupal 7 code does not always do.

  • Pipeline finished with Success
    2 months ago
    Total: 121s
    #672417
Production build 0.71.5 2024