Skip to content

Instantly share code, notes, and snippets.

@adamwathan
Last active June 11, 2022 19:55
Show Gist options
  • Select an option

  • Save adamwathan/984914b2eee8e4d79a06f7045e4ce999 to your computer and use it in GitHub Desktop.

Select an option

Save adamwathan/984914b2eee8e4d79a06f7045e4ce999 to your computer and use it in GitHub Desktop.
Multiformat Endpoints in Laravel
<?php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Route;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
class CaptureRequestExtension
{
public function handle($request, $next)
{
if ($request->route()->parameter('_extension') !== null) {
$request->attributes->set('_extension', substr($request->route()->parameter('_extension'), 1));
$request->route()->forgetParameter('_extension');
}
return $next($request);
}
}
<?php
Request::macro('match', function ($responses, $defaultFormat = 'html') {
if ($this->attributes->get('_extension') !== null) {
return value(array_get($responses, $this->attributes->get('_extension'), function () {
abort(404);
}));
}
return value(array_get($responses, $this->format($defaultFormat)));
});
<?php
Route::macro('multitype', function () {
if (count($this->parameterNames()) > 0 && ends_with($this->uri(), '}')) {
$lastParameter = array_last($this->parameterNames());
if (! isset($this->wheres[$lastParameter])) {
$this->where($lastParameter, '[^.]+');
}
}
$this->uri = $this->uri . '{_extension?}';
$this->where('_extension', '(\..+)');
$this->middleware(CaptureRequestExtension::class);
$this->parameterNames = $this->compileParameterNames();
return $this;
});
<?php
// In routes file:
Route::get('/podcasts/{id}', 'PodcastsController@show')->multitype();
// In controller:
public function show($id)
{
$podcast = Podcast::findOrFail($id);
abort_unless($podcast->isVisibleTo(Auth::user()), 404);
return request()->match([
'html' => view('podcasts.show', [
'podcast' => $podcast,
'episodes' => $podcast->recentEpisodes(5),
]),
'json' => $podcast,
]);
}
@vtalbot
Copy link

vtalbot commented Feb 15, 2018

Shouldn't the 'html' in 3-use-it.php line 29 return a closure like xml to avoid useless processing?

BTW, really awesome example!

@markhuot
Copy link

@vtalbot, the view() will automatically become a 200 text/html reponse. Without the closure the XML response would do the same. The closure ensures the XML response has the correct Content-type.

It would be over engineered, but fun, if the response could automatically see a SimpleXML implementation, or some other signifier, and set the content type automatically. That’d be a nice reason to fork this.

@BrandonSurowiec
Copy link

You can probably shorten the macro to this:

        Request::macro('match', function ($responses, $defaultFormat = 'html') {
            return value(array_get($responses, $this->route()->parameter('_extension') ?? $this->format($defaultFormat), function () {
                abort(404);
            }));
        });

@m1guelpf
Copy link

Extracted to a package, now you can just do:

composer require m1guelpf/laravel-multiformat

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