Skip to content

Instantly share code, notes, and snippets.

@danharper
Last active August 1, 2017 12:06
Show Gist options
  • Select an option

  • Save danharper/8597149 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

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
Copy Markdown

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