Sending HTML or JSON response depending on the cli

2019-04-10 13:36发布

问题:

I have a Laravel application with Eloquent entities and their respective RESTful resource controllers, like the following:

The model

class Entity extends Eloquent {
    ...
}

The controller

class EntityContoller {

    public function index() {
        Entity $entities = Entity::all();
        return view('entity.index', compact($entities));
    }

    ... // And many more routes like that
}

Now I am building an android application, and instead of returning views, I need the data as JSON.

In my current solution, for every request I make from my Android application, I add a get query parameter contentType=JSON. I detect that in the controller, and send the data accordingly like the following. But this seems tedious, and I have to write the same condition everywhere.

class EntityContoller {

    public function index() {
        Entity $entities = Entity::all();

        if(Request::get('contentType', 'JSON')) return $entities;
        return view('entity.index', compact($entities));
    }

    ... // And many more routes like that
}

What is the best way I can do this, without having to write this condition in every controller action?

回答1:

If you don't want to change your controllers, then you could use a middleware that alters the response after it is returned from the controller.

The middleware would receive the response from the controller, check for contentType == JSON and then return the proper response.

The middleware would look like this:

use Closure;
class JsonMiddleware {
    public function handle($request, Closure $next) {
        // Get the response from the controller
        $response = $next($request);

        // Return JSON if necessary
        if ($request->input('contentType') == 'JSON') {
            // If you want to return some specific JSON response
            // when there are errors, do that here.

            // If response is a view, extract the data and return it as JSON
            if (is_a($response, \Illuminate\View\View::class)) {
                return response()->json($response->getData());
            }
        }

        return $response;
    }
}

You would then register the middleware in app/Http/Kernel.php by appending it to the $routeMiddleware array.

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    // New Middleware
    'json' => \App\Http\Middleware\JsonMiddleware::class,
];

Then you would just assign the middleware to routes that might return JSON.

Route::get('user/{user_id}', ['middleware' => 'json', 'uses' => 'App\UserController@getUser']);

You can read more about middleware here and registering and assigning middleware here.

You can read about sending JSON responses in Laravel here.



回答2:

How about a custom response class? Let me give you an idea. Create a new class or service:

class ResourceResponse{

    public function __construct($request, $object, $view = null){
        // setting values
    }

    public function __toString(){
        if ($this->request->get('contentType') == 'JSON'){
            return $this->object;
        }

        // view as fallback
        return view($this->view, compact($this->object));
    }
}

Then in your resources just call the function like using the view name as a fallback if json type is not specified:

public function index(Request $request) {
    $entities = Entity::all();
    return new ResourceResponse($request, $entities, 'entity.index'); 
}

If you are pretty sure that the response should be JSON format, then you can forget the view name:

public function index(Request $request) {
    $entities = Entity::all();
    return new ResourceResponse($request, $entities); 
}

$request parameter at index() method make use of type-hint as described at Laravel 5's docs



回答3:

First create BaseController with protected function createView

class BaseController extends Controller
{
    protected function createView($object)
    {
        if (Request::get('contentType', 'JSON')) return $object;
        return view('entity.index', compact($object));
    }
}

Then in every controller extends this Base Controller

class EntityController extends BaseController
{
    public function index()
    {
        $entities = Entity::all()
        return $this->createView($entites);
    }

    public function get($id)
    {
        $entity = Entity::find($id);
        return $this->createView($entity);
    }
}

If there any others exception like multiple object or error, there is no other way to handle it individually.