Skip to content

Instantly share code, notes, and snippets.

@danharper
Last active August 1, 2017 12:06
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save danharper/8597149 to your computer and use it in GitHub Desktop.
Save danharper/8597149 to your computer and use it in GitHub Desktop.
A Laravel Controller which allows you to display API/report data in multiple formats. For example, you may display a preview as HTML, and offer buttons to download as CSV and JSON.
<?php
// this is the base controller which parses output to HTML/CSV/JSON depending on the format in the URL
use Illuminate\Support\Collection;
class FormatController extends Controller {
protected $fileName = 'export';
protected $view = 'reports.output';
public function callAction($method, $params)
{
$response = parent::callAction($method, $params);
// default JSON. you may wish to change this to be HTML instead
if ( ! isset($params['format'])) return $response;
if ($params['format'] == '.csv')
{
return $this->asCsv($response);
}
if ($params['format'] == '.html')
{
return $this->asHtml($response);
}
return $response;
}
protected function asCsv($response)
{
$csv = '';
if ($response && count($response))
{
ob_start();
$handle = fopen('php://output', 'r+');
$first = ($response instanceof Collection) ? $response->first() : reset($response);
fputcsv($handle, array_keys($first));
foreach ($response as $r)
{
fputcsv($handle, array_values($r));
}
$csv = ob_get_clean();
fclose($handle);
}
return Response::make($csv, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment;filename='.$this->fileName.'.csv'
]);
}
protected function asHtml($response)
{
$path = '/'.str_replace('.html', '', Request::path());
$query = Request::getQueryString();
$first = ($response instanceof Collection) ? $response->first() : reset($response);
return View::make($this->view)
->with('title', $this->fileName)
->with('headers', $first ? array_keys($first) : [])
->with('rows', $response)
->with('error', $first ? null : 'No data found for criteria.')
->with('urls', (object) [
'csv' => $path.'.csv?'.$query,
'json' => $path.'.json?'.$query,
]);
}
}
<?php
// this is an example Report controller making use of the FormatController
// we just return a collection of models.
// also included is an example of a date range filter
use Carbon\Carbon;
class ReportController extends FormatController {
public function __construct()
{
Carbon::setToStringFormat('d/m/y');
}
public function getCompletedOrders()
{
$this->fileName = 'Completed Orders';
list($from, $to) = $this->getDateRange();
return Order::where('status', Order::COMPLETE)->whereBetween('completed_at', [$from, $to])->get();
}
protected function getDateRange()
{
$from = Input::has('from') ? new Carbon(Input::get('from')) : null;
$to = Input::has('to') ? new Carbon(Input::get('to')) : null;
return [$from, $to];
}
}
<?php
// an example route. the {format} piece is important
Route::get('reports/completed-orders{format}', 'ReportController@getCompletedOrders');
<!--
an example view which will work out-of-the-box with the FormatController.
just place this at "reports/output.blade.php" (as defined in the $view variable in FormatController)
-->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title }} Export</title>
</head>
<body>
<p><a href="/reports">&laquo; Back to Reports</a></p>
<h1>{{ $title }}</h1>
<p>
Export as: &nbsp;<a class="btn" href="{{ $urls->csv }}">CSV</a> <a class="btn" href="{{ $urls->json }}">JSON</a>
</p>
@if ($error)
<p class="error">
{{ $error }}
</p>
@endif
<table>
<thead>
<tr>
@foreach ($headers as $header)
<th>{{ $header }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach ($rows as $row)
<tr>
@foreach (array_values($row) as $value)
<td>{{ $value }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</body>
</html>
@sheldonkotyk
Copy link

If you are getting array_keys() expects parameter 1 to be array, object given errors, add the toArray lines in FormatController.php

$first = ($response instanceof Collection) ? $response->first() : reset($response);

            $first = $first->toArray();
            $response = $response->toArray();

You'll need to add to the html and csv blocks

@MattWohler
Copy link

Care to make the equivalent that adhere's to the SOLID principles? I'm having trouble designing for Liskov as it states each implementation of the interface (Csv/Html) should return the same data type/structure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment