【问题标题】:Sending HTML or JSON response depending on the client根据客户端发送 HTML 或 JSON 响应
【发布时间】:2015-11-29 17:25:02
【问题描述】:

我有一个 Laravel 应用程序,其中包含 Eloquent 实体及其各自的 RESTful resource controllers,如下所示:

型号

class Entity extends Eloquent {
    ...
}

控制器

class EntityContoller {

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

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

现在我正在构建一个 android 应用程序,而不是返回视图,我需要 JSON 格式的数据。

在我当前的解决方案中,对于我从 Android 应用程序发出的每个请求,我都会添加一个 get 查询参数 contentType=JSON。我在控制器中检测到这一点,并相应地发送数据,如下所示。但这似乎很乏味,我必须在任何地方都写相同的条件。

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
}

我可以做到这一点的最佳方法是什么,而不必在每个控制器操作中都写这个条件?

【问题讨论】:

    标签: php rest laravel laravel-5


    【解决方案1】:

    如果您不想更改控制器,那么您可以使用middleware 来更改响应之后从控制器返回。

    中间件会收到来自控制器的响应,检查contentType == JSON,然后返回正确的响应。

    中间件如下所示:

    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;
        }
    }
    

    然后,您可以通过将中间件附加到 $routeMiddleware 数组来在 app/Http/Kernel.php 中注册中间件。

    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,
    ];
    

    然后您只需将中间件分配给可能返回 JSON 的路由。

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

    您可以阅读有关中间件here 以及注册和分配中间件here 的更多信息。

    您可以阅读有关在 Laravel here 中发送 JSON 响应的信息。

    【讨论】:

    • 这似乎是一个不错的解决方案。我喜欢这样一个事实,即我可以将 json 中间件添加到控制器中的特定功能或所有这些功能中。或者,我什至可以轻松添加更多类型的响应,例如移动和桌面显示的不同视图。
    【解决方案2】:

    自定义响应类怎么样?让我给你一个想法。创建一个新的类或服务:

    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));
        }
    }
    

    如果未指定 json 类型,则只需在您的资源中调用该函数,例如使用视图名称作为后备:

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

    如果您非常确定响应应该是 JSON 格式,那么您可以忘记视图名称:

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

    index() 方法中的$request 参数使用Laravel 5's docs 中描述的类型提示

    【讨论】:

      【解决方案3】:

      首先用受保护的函数createView创建BaseController

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

      然后在每个控制器中扩展这个基本控制器

      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);
          }
      }
      

      如果有其他异常,如多对象或错误,没有其他方法可以单独处理。

      【讨论】:

      • 我必须在我拥有的每个控制器中创建这个函数。此外,一些函数可能返回多个对象,或者它们可能重定向到不同的路由,或者抛出错误,或者返回旧的输入(以防请求中存在某些验证)。
      【解决方案4】:

      除了 BrokenBinary 的答案:在 Laravel 8 中,$response 永远不是Illuminate\View\View 的实例。但是,如果您在$response 上致电getOriginalContent(),则可以得到它。

      use Closure;
      use Illuminate\Http\Request;
      use Illuminate\Http\Response;
      use Illuminate\View\View;
      
      class JsonMiddleware {
          public function handle(
              Request $request,
              Closure $next
          ) {
              // Get the response from the controller
              $response = $next( $request );
      
              // Return JSON if necessary
              if ( $request->expectsJson() ) {
                  // 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 ( $this->isView( $response ) ) {
                      $originalContent = $response->getOriginalContent();
      
                      return response()->json(
                          $originalContent->getData()
                      );
                  }
              }
      
              return $response;
          }
      
          private function isView( Response $response ) {
              return (
                  $response->getOriginalContent() instanceof View
              );
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2018-10-12
        • 2018-06-20
        • 2017-08-11
        • 2018-08-30
        • 2020-12-31
        • 2016-01-20
        • 2013-09-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多