In the next Laravel version 5.5 a new interface called Responseable
gets introduced. It is a simple interface that defines one method called toResponse
that returns an instance of Illuminate\Http\Response
.
This new feature is looking really nice. Especially for the project I’m currently working on at work. We use a variety of datasources (php api’s, elasticsearch, cache, …) in our controllers. This leads to a very unstructured and big controller file.
In addition to multiple datasources we use Handlebars for template rendering on the server side and (of course) on the client side to rerender parts of our application. To be able to use handlebars in PHP we need to convert all our view data into arrays.
This situation led us to use a Laravel anti pattern - View Models 😱. We need this extra layer to be able to prepare our models (from the different sources) for our views.
Now let’s look at a simple example:
<?php
class DemoController {
public function index()
{
// ...
$user = $api->fetch($userId);
$company = $elasticsearch->find($companyId);
// ...
$viewData = $demoIndexViewModel->prepare($user, $company);
if (request()->ajax()) {
return response()->json($viewData);
} else {
return response()->view('demo.index', $viewData);
}
}
}
At first we fetch our data, then we call our controller view model (or multiple if the controller is too simple). The last if-else structure can be found in every controller and is needed to either send the view data directly to client for rerendering or to fully render the page.
First thing would be to introduce ControllerActionResponse classes that will be responsible for the View Model stuff and the response preparation. To be able to remove the code duplications a BaseResponse class is used that does the Responsable
implementation and defines an abstract method for preparing our data.
<?php
abstract class BaseResponse implements Responsable {
/**
* @var string
*/
protected $view;
abstract protected function prepare() : array;
public function toResponse()
{
$data = $this->prepare();
if (request()->ajax()) {
return response()->json($data);
} else {
return response()->view($this->view, $data);
}
}
}
For our DemoController@index
action we can now implement a DemoIndexResponse
that extends BaseResponse
. It is responsible for doing the model to view transformation, in our case with view models.
<?php
class DemoIndexResponse extends BaseResponse {
// ... variables and ViewModel initialization
public function __construct(User $user, Company $company)
{
$this->user = $user;
$this->company = $company;
$this->view = 'demo.index';
}
protected function prepare()
{
return array_merge(
$this->userViewModel->prepare($this->user),
$this->companyViewModel->prepare($this-company)
);
}
}
This leads to a clean controller action that is responsible for fetching the required data and passing it to the custom response class.
<?php
class DemoController {
public function index()
{
// ...
$user = $api->fetch($userId);
$company = $elasticsearch->find($companyId);
// ...
return new DemoIndexResponse($user, $company);
}
}
The Responsable
feature helps a lot to clean up our controller actions. It moves the response logic from the controller into its own layer. Of course it does not solve the problem with the view models but it gives us a starting point for rethinking the whole data preparation layer.