Instantly share code, notes, and snippets.
Created
July 20, 2018 00:41
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save hailwood/b2bd0ca36c6f94754ea30613ee68bfbd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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