0

I have a View of song nodes, and each node has a programmatically calculated total number of points associated with it. (For background: song nodes are referenced in a custom field_chart_items field on a chart node, hence the $chartHistory node query seen in the hook below. The custom field includes a numeric Position that's entered on the chart node, and a numeric Points total that's hidden from view and calculated based on Position in a chart_field_entity_presave() hook.) I'm trying to sort the View's results by points, with highest points totals listed first.

In a views_pre_render() hook, I calculate the points total for each row and place it into an array, $songAndPoints, that contains the song's nid and points total. The array is sorted so that the song with the highest points total comes first – therefore, the order of the nids in the array is the order in which I want my results to appear.

Can I programmatically reorder the $view->result array based on the array of nids I'm getting? My attempt below, using a uasort() callback, doesn't work, but I'm not sure if or how I can do what I'm looking for (or if, perhaps, it needs to happen in a different hook).

<?php
/**
 * Implements hook_views_pre_render().
 */
function MODULE_views_pre_render(&$view) {
  if ($view->current_display === 'DISPLAYID'
    && $view->id() === 'VIEWID') {
      $entity_type_manager = \Drupal::entityTypeManager();
      $node_storage = $entity_type_manager->getStorage('node');  

      foreach ($view->result as $key => $song) {
        $node_type = 'chart';
        $songNID = $song->nid;
        
        $chartHistory = $node_storage->getQuery()
          ->condition('type', $node_type)
          ->condition('field_chart_items', $songNID, 'IN')
          ->condition('status', 1)
          ->execute();

        if ($chartHistory) {
          foreach ($chartHistory as $chartNID) {
            // ...
            // Various calculations to get points total, $songPoints, for each song.
            // ...
          }
          $songAndPoints[$key] = ['points' => $songPoints, 'nid' => $songNID];

          // Sort based on point values.
          arsort($songAndPoints);

        } else {
          $songAndPoints[] = ['points' => 0, 'nid' => $songNID];
        }
      }

      kint($songAndPoints); // Array with original row number as key and two values, points and NID, sorted with highest points first. This is how I want to sort the results.

      uasort($view->result, "MODULE_sort_by_points_total");
  }
}

/**
 * Custom uasort() callback to sort view results by points totals.
 */
function MODULE_sort_by_points_total($left, $right) {
    $order = ['704', '712', '707', '713', '711', '709', '708', '883', '882', '715', '714', '710', '705']; // TEMPORARY: Hardcoded list of node IDs in order; should be replaced by something like array_column($songAndPoints, 'nid'), but hardcoded values don't work either.

    $flipped = array_flip($order);
    $leftPos = $flipped[$left]->nid;
    $rightPos = $flipped[$right]->nid;
    return $leftPos >= $rightPos;
    // via https://stackoverflow.com/questions/17324708/php-array-multisort-not-sorting-my-multidimensional-array-as-expected/17325584#17325584
}

1 Answer 1

2

I got my answer via the Drupal Slack, but reposting it here for anyone who might come across this later, since that's not publicly searchable. It was more of a PHP issue than anything Drupal-specific.

Within the uasort() callback, the $left and $right arguments are objects of the ResultRow class, with nid available from them, so switching $leftPos and $rightPos to $flipped[$left->nid] and $flipped[$right->nid], respectively, gets me access to the results' nids.

/**
 * Custom uasort() callback to sort view results by points totals.
 */
function MODULE_sort_by_points_total($left, $right) {
    $order = ['704', '712', '707', '713', '711', '709', '708', '883', '882', '715', '714', '710', '705'];

    $flipped = array_flip($order);
    $leftPos = $flipped[$left->nid];
    $rightPos = $flipped[$right->nid];
    return $leftPos >= $rightPos;
}

Continuing on, I figured out that switching it to an anonymous function was what I needed to pass $songAndPoints to the uasort() function, as follows, instead of separating it:

$sortByPointsTotal = function($left, $right) use ($songAndPoints) {
    $order = array_column($songAndPoints, 'nid');
    
    $flipped = array_flip($order);
    $leftPos = $flipped[$left->nid];
    $rightPos = $flipped[$right->nid];
    return $leftPos >= $rightPos;
};

uasort($view->result, $sortByPointsTotal);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.