Created
February 14, 2025 02:51
-
-
Save kohenkatz/18885b4a783fe91faf10e6613eb2b93a to your computer and use it in GitHub Desktop.
A Laravel class for content negotiation in JSON or flat-file (CSV/XLS(X)/etc)
This file contains hidden or 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 | |
namespace App\Http\Responses; | |
use Closure; | |
use Illuminate\Contracts\Support\Responsable; | |
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; | |
use Illuminate\Database\Eloquent\Relations\Relation; | |
use Illuminate\Database\Query\Builder; | |
use Illuminate\Http\Resources\Json\JsonResource; | |
use InvalidArgumentException; | |
use Maatwebsite\Excel\Concerns\FromQuery; | |
use Spatie\QueryBuilder\QueryBuilder; | |
use Symfony\Component\HttpFoundation\Response; | |
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; | |
/** | |
* Implements an HTTP-compliant Content Negotiation response. | |
* | |
* Given a Laravel "JsonResource" transformer and a Laravel-Excel "Exporter", | |
* the incoming request's `Accept` header is used to choose the appropriate response | |
* format. | |
* | |
* In the event the incoming request indicates that any response type is accepted | |
* (using `*\/*`), JSON is used as the default. If the requested type is not supported, | |
* a spec-compliant 406 response is returned. | |
* | |
* This class is partially inspired by the Ruby on Rails `ActionController#respond_to` method. | |
* | |
* @package App\Http\Responses | |
*/ | |
class Negotiable implements Responsable | |
{ | |
protected const SUPPORTED_DOWNLOAD_TYPES = [ | |
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => \Maatwebsite\Excel\Excel::XLSX, | |
'application/vnd.ms-excel' => \Maatwebsite\Excel\Excel::XLS, | |
'application/vnd.oasis.opendocument.spreadsheet' => \Maatwebsite\Excel\Excel::ODS, | |
'text/csv' => \Maatwebsite\Excel\Excel::CSV, | |
]; | |
public function __construct( | |
protected Builder|EloquentBuilder|Relation|QueryBuilder $query, | |
protected string $jsonResource, | |
protected string $exportClass, | |
protected null|array|Closure $additionalData = null, | |
protected string $name = 'data', | |
) | |
{ | |
if (!is_a($jsonResource, JsonResource::class, allow_string: true)) { | |
throw new InvalidArgumentException("$jsonResource must be a valid JsonResource"); | |
} | |
if (!is_a($exportClass, FromQuery::class, allow_string: true)) { | |
throw new InvalidArgumentException("$exportClass must be a valid Laravel-Excel Exporter"); | |
} | |
} | |
/** | |
* Create an HTTP response that represents the object. | |
* | |
* @param \Illuminate\Http\Request $request | |
* @return \Symfony\Component\HttpFoundation\Response | |
*/ | |
public function toResponse($request): Response | |
{ | |
// Check for JSON | |
if ($request->accepts('application/json')) { | |
return tap(($this->jsonResource)::collection($this->query->get()), function (JsonResource $r) { | |
if ($this->additionalData) { | |
$r->additional(value($this->additionalData)); | |
} | |
})->toResponse($request); | |
} | |
// Check for any supported flat-file type | |
if (($mimeType = $request->prefers(array_keys(static::SUPPORTED_DOWNLOAD_TYPES)))) { | |
$type = static::SUPPORTED_DOWNLOAD_TYPES[$mimeType]; | |
$name = $this->name . '.' . strtolower($type); | |
return (new ($this->exportClass)($this->query)) | |
->download( | |
$name, | |
$type, | |
[ | |
'Content-Type' => $mimeType, | |
], | |
); | |
} | |
throw new NotAcceptableHttpException('Unrecognized content type'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment