You are here

function coder_upgrade_convert_return in Coder 7

Same name and namespace in other branches
  1. 7.2 coder_upgrade/conversions/tool.inc \coder_upgrade_convert_return()

Initiates the transformation of array assignments in a hook.

Applies to: hook_action_info(), hook_hook_info(), hook_node_info(), hook_theme().

NOTE In general, there are 3 typical cases (or code styles):

  • return array('key1' => array('key2' => ...);
  • $var = array('key1' => array('key2' => ...); return $var;
  • $var = array(); $var['key1'] = array('key2' => ...); return $var;

The inner array to modify is 3 levels deep in the first 2 cases, but only 2 levels deep in the third. In the first 2 cases, we can loop on the key1 arrays. In the third, the loop is on assignment statements.

This new routine was failing on hook_hook_info() because the keys are not distinguishable. Ie, we can not tell what level of the array we are on if the array is inline and has 3 levels. Previously, return_case1() would strip off the first layer and get to the second level. There is no guarantee of nice code anyway.

This new routine was failing on capturing drupal_get_form() callbacks on case 5, like example_admin_form3() defined in $items[$admin_path]['page arguments'][] = 'example_admin_form3'. This again relates to a depth parameter and that we are always looking for an array.

Should we pass a depth parameter, or figure it dynamically?

Parameters

PGPList $body: List of statements in a block.

string $hook: Name of the hook being modified.

string $callback: A string of the callback function for the hook.

integer $start_depth: The starting depth of nested arrays to traverse.

integer $remaining_depth: The remaining depth of nested arrays to traverse. When equal to zero, stop.

6 calls to coder_upgrade_convert_return()
coder_upgrade_cache_info_hooks in coder_upgrade/conversions/begin.inc
Caches hook_theme() and hook_menu() entries for the modules being converted.
coder_upgrade_upgrade_hook_action_info_alter in coder_upgrade/conversions/function.inc
Implements hook_upgrade_hook_action_info_alter().
coder_upgrade_upgrade_hook_hook_info_alter in coder_upgrade/conversions/function.inc
Implements hook_upgrade_hook_hook_info_alter().
coder_upgrade_upgrade_hook_node_info_alter in coder_upgrade/conversions/function.inc
Implements hook_upgrade_hook_node_info_alter().
coder_upgrade_upgrade_hook_perm_alter in coder_upgrade/conversions/function.inc
Implements hook_upgrade_hook_perm_alter().

... See full list

File

coder_upgrade/conversions/tool.inc, line 320
Provides tools to assist with conversion routines.

Code

function coder_upgrade_convert_return(&$body, $hook, $callback = '', $start_depth = 0, $remaining_depth = -1) {

  // DONE
  cdp("inside " . __FUNCTION__);

  // Initialize.
  $editor = PGPEditor::getInstance();
  $callback = $callback == '' ? "coder_upgrade_callback_{$hook}" : $callback;
  $msg = '// TODO The array elements in this hook function need to be changed.';

  // Get a list of return statements in the body statements.
  $nodes = $body
    ->searchAll('PGPFunctionCall', 'name', 'value', 'return', TRUE);

  // Keep track of return variables to avoid redundant searching.
  $already_searched = array();
  while (!empty($nodes)) {
    cdp('while (!empty($nodes)) ' . __FUNCTION__);
    $return_node = array_shift($nodes);
    $return = $return_node->data;

    // Evaluate the return operand.
    if (get_class($return) == 'PGPFunctionCall') {
      $value = $return
        ->getParameter();
      $depth = 0;
    }
    elseif (get_class($return) == 'PGPAssignment') {

      // Get the operands to the right of the assignment operator.
      $value = $return
        ->getValue();
      cdp($return
        ->toString(), '$return AFTER');

      // Evaluate the "depth" of the assignment based on number of indices in
      // the assignment variable.
      // Examples: the operand on the RHS is at
      //   level 1: $var = array(key => array(..), ..)
      //   level 2: $var[key1] = array(..)
      //   level 3: $var[key1][key2] = array(..)
      // This mimics what was done in return_caseN(). We went down to level 2
      // from case1 to case3.
      $assign_variable = $return->values
        ->getElement()
        ->getType('operand')
        ->stripComments();

      // @todo This should handle most cases, but will fail depending on code style.
      // If depth is > start_depth (like case5), then reconstruct an array at
      // the desired depth using toString() and reparsing (see case5).
      $depth = $assign_variable
        ->countType('index');
      if ($depth > $start_depth) {

        // @todo This works in some use cases (menu stuff in begin.inc). If we
        // need to change the original expression, this fails because $value is
        // now disjoint from the original expression.
        $value = coder_upgrade_reconstruct_array($assign_variable, $value);
        $depth = 1;

        // $assign_variable->countType('index');
      }
    }
    cdp($value
      ->toString(), '$value');
    $occurrence = 1;

    // Loop on all operands in expression (e.g. $array + array(..)).
    while ($occurrence < $value
      ->countType('operand') + 1) {
      $operand = $value
        ->getType('operand', $occurrence);
      if ($operand) {
        cdp('inside if ($operand)');
        if (!is_object($operand)) {

          // @todo This hits stuff like $items[$admin_path]['page arguments'][] = 'example_admin_form3';
          cdp('!is_object($operand)');
          cdp($operand, '$operand');
          $occurrence++;
          continue;
        }
        cdp($operand
          ->toString(), '$operand');
        if (get_class($operand) == 'PGPArray') {

          // Use case 1 - returns array directly.
          $operand
            ->traverse2($return_node, $hook, $callback, $start_depth - $depth, $remaining_depth);
        }
        elseif (get_class($operand) == 'PGPOperand') {

          // Avoid redundant searching.
          if (in_array($operand
            ->stripComments()
            ->toString(), $already_searched)) {
            $occurrence++;
            continue;
          }

          /*
           * Search body statements for all assignments to the return variable.
           * The assignment could be to an array element like $info['node_type_name'] = array(...).
           * Or directly to the variable like $info = array('node_type_name' => array(...)).
           */
          $already_searched[] = $return_variable = $operand
            ->stripComments()
            ->toString();

          // @todo Limit search to nodes preceding the statement.
          $nodes = array_merge($nodes, $body
            ->searchAll('PGPAssignment', 'values', 0, $return_variable, TRUE));
        }
        else {

          // @todo This message is not accurate -- this error hits on the array value being a string or function call.
          clp("ERROR: operand of return statement is not an array or variable in hook_{$hook}");
          cdp("ERROR: operand of return statement is not an array or variable in hook_{$hook}");
          cdp($operand, '$operand');
          $body
            ->insertFirst($editor
            ->commentToStatement($msg), 'comment');
        }
      }
      $occurrence++;
    }
  }
}