Skip to content

Instantly share code, notes, and snippets.

@hailwood
Created July 20, 2018 00:41
Show Gist options
  • Save hailwood/b2bd0ca36c6f94754ea30613ee68bfbd to your computer and use it in GitHub Desktop.
Save hailwood/b2bd0ca36c6f94754ea30613ee68bfbd to your computer and use it in GitHub Desktop.
<?php
/**
* Improved GridFieldExportButton that streams CSV data to the client instead of building
* the entire CSV in memory and sending that (which doesn't work for large data sets).
*/
class GridFieldStreamExportButton extends GridFieldExportButton
{
/**
* Modified export code to stream a CSV instead of SilverStripe's "build and
* send" approach, which is memory constrained
*
* @param GridField $gridField
* @param SS_HTTPRequest $request
* @return void
*/
public function handleExport($gridField, $request = null)
{
$headers = [];
$fieldMap = $this->getExportColumnsForGridField($gridField);
// Map source names to display names
// If a field is callable (e.g. anonymous function) then use the source name as the header
foreach ($fieldMap['display'] as $columnSource => $columnHeader) {
if (!is_string($columnHeader) && is_callable($columnHeader)) {
$headers[] = $columnSource;
} else {
$headers[] = $columnHeader;
}
}
$this->streamCsv($gridField, $headers, $fieldMap);
}
/**
* Return the columns to export
*
* @param GridField $gridField
*
* @return array
*/
protected function getExportColumnsForGridField(GridField $gridField) {
$exportColumns = ['formatting' => [], 'display' => []];
/** @var \GridFieldDataColumns $dataCols */
if($this->exportColumns) {
$exportColumns['display'] = $this->exportColumns;
} else if($dataCols = $gridField->getConfig()->getComponentByType('GridFieldDataColumns')) {
$exportColumns['display'] = $dataCols->getDisplayFields($gridField);
$exportColumns['formatting'] =$dataCols->getFieldFormatting();
} else {
$exportColumns['display'] = singleton($gridField->getModelClass())->summaryFields();
}
return $exportColumns;
}
public function getHTMLFragments($gridField) {
$button = new GridField_FormAction(
$gridField,
'export',
'Export to CSV (s)',
'export',
null
);
$button->setAttribute('data-icon', 'download-csv');
$button->addExtraClass('no-ajax action_export');
$button->setForm($gridField->getForm());
return array(
$this->targetFragment => '<p class="grid-csv-button">' . $button->Field() . '</p>',
);
}
/**
* Generate an stream a CSV file to the browser, without creating it all in memory first
*
* @param GridField $gridField
* @param array $headers
* @param array $fieldMap
*/
public function streamCsv(GridField $gridField, array $headers, array $fieldMap)
{
$exportName = $this->getExportFilename($gridField->getModelClass());
header("Content-Type: text/csv");
header("Content-Disposition: attachment; filename=\"{$exportName}\"");
// DataList iterates by loading all rows into memory
// This doesn't work with huge numbers of records, so we iterate over query results manually here
/** @var SQLQuery $query */
$class = $gridField->getModelClass();
$query = $gridField->getList()
->limit(0)
->dataQuery()
->query();
// Open and output headers
$stream = fopen('php://output', 'w');
fputcsv($stream, $headers);
foreach ($query->execute() as $row) {
$record = new $class($row);
fputcsv($stream, $this->mapRecordToCsvRow($record, $fieldMap, $gridField));
}
fclose($stream);
die();
}
/**
* @param DataObject $item
* @param array $fieldMap
* @param GridField $gridField
* @return array
*/
protected function mapRecordToCsvRow(DataObject $item, array $fieldMap, GridField $gridField)
{
$row = [];
foreach ($fieldMap['display'] as $columnSource => $columnHeader) {
if (!is_string($columnHeader) && is_callable($columnHeader)) {
if ($item->hasMethod($columnSource)) {
$relObj = $item->{$columnSource}();
} else {
$relObj = $item->relObject($columnSource);
}
$value = $columnHeader($relObj);
} else {
$value = $gridField->getDataFieldValue($item, $columnSource);
}
$row[] = isset($fieldMap['formatting'][$columnSource]) ? $fieldMap['formatting'][$columnSource]($value, $item, true) : $value;
}
return $row;
}
/**
* @return string
*/
protected function getExportFilename($modelClass)
{
$now = Date("Y-m-d-H-i");
return "export-$now.csv";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment