Add ability to alter rows in xls export

Created on 27 June 2023, about 2 years ago

Problem/Motivation

I needed to alter individual rows, because xls_serialization module does not support hook_views_view_field() where all my alterations have been already made. So I borrowed solution from parent views_data_export module and added the hook to the code.

Proposed resolution

I do not know how to write the patch so I will post the code here, if someone can create patch it would be highly appreciated!

1. File /xls_serialization/src/Plugin/views/style/ExcelExport.php

Added render() function at the bottom of the file:

/**
   * {@inheritdoc}
   */
  public function render() {
	  
    // This is pretty close to the parent implementation.
    // Difference (noted below) stems from not being able to get anything other
    // than json rendered even when the display was set to export csv or xml.
    $rows = [];
    foreach ($this->view->result as $row_index => $row) {
      $this->view->row_index = $row_index;
      $output = $this->view->rowPlugin->render($row);
      \Drupal::moduleHandler()->alter('xls_serialization_row', $output, $row, $this->view);
      $rows[] = $output;
    }

	unset($this->view->row_index);

    // Get the format configured in the display or fallback to json.
    // We intentionally implement this different from the parent method because
    // $this->displayHandler->getContentType() will always return json due to
    // the request's header (i.e. "accept:application/json") and
    // we want to be able to render csv or xml data as well in accordance with
    // the data export format configured in the display.
    $format = !empty($this->options['formats']) ? reset($this->options['formats']) : 'json';

    return $this->serializer->serialize($rows, $format, ['views_style_plugin' => $this]);

  }

2. Added new file xls_serialization.api.php where I put following code since hook_xls_serialization_row_alter() should now be available:

/**
 * @addtogroup hooks
 * @{
 */

function hook_xls_serialization_row_alter(&$row, \Drupal\views\ResultRow $result, \Drupal\views\ViewExecutable $view) {
  if ($view->id() == 'my_view') {
    $row['custom_field'] = my_function($result['nid']);
  }
}

/**
 * @} End of "addtogroup hooks".
 */

What I am struggling with is cacheability of the hook. It looks like hook is being executed only once and the code is being cached. I guess this is not a problem since even row alterations should be cached in order to speed up the rendering process but it is pain to debug and work with. I was unable to remove caching though.

I hope it helps someone in the future.

✨ Feature request
Status

Active

Version

1.0

Component

Code

Created by

πŸ‡ΈπŸ‡°Slovakia lubwn

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

Merge Requests

Comments & Activities

  • Issue created by @lubwn
  • πŸ‡«πŸ‡·France mably

    Wondering what could be the impact on performance on huge dataset...

    Could it be problematic or not?

  • πŸ‡«πŸ‡·France mably

    @lubwn ChatGPT suggest a better approach, on a performance point of view. Would it still work for your use case?

    Better Approach: Call the Hook Once

    Instead of calling the hook on each row, call it once before the loop, and then apply the returned logic within the loop.

    πŸ”§ Step-by-step

    Step 1: Let hook implementations return callables or config

    In other modules:

    function my_module_my_custom_hook_prepare() {
      return function ($row) {
        // Do something with $row
        // e.g. modify, collect stats, tag, etc.
      };
    }

    Step 2: Invoke the hook once and collect callables

    $callbacks = \Drupal::moduleHandler()->invokeAll('my_custom_hook_prepare');

    This will return an array of functions (closures or callables) from all modules implementing hook_my_custom_hook_prepare().

    Step 3: Use those callables inside the loop

    foreach ($view->result as $row) {
      foreach ($callbacks as $callback) {
        $callback($row);
      }
    }

    βœ… Why This Is Better

    πŸ”„ Before

    • For n rows and m modules, you have O(n Γ— m) hook resolution calls.
    • Even for rows where no logic is needed, you're still scanning all modules.

    ⚑ After

    • Hook resolution (invokeAll()) happens only once.
    • For n rows, you directly apply the relevant logic.
    • Much easier to cache or optimize later.
  • πŸ‡«πŸ‡·France mably

    This feature can now be tested in the new Excel Serialization Extras β†’ sandbox module.

  • πŸ‡«πŸ‡·France mably

    Actually, the Drupal hook cache is pretty performant, so not sure the ChatGPT optimisation suggestion is really necessary.

Production build 0.71.5 2024