Refactor fat Laravel controllers using Responsable interface
When you write Laravel applications, controllers can easily get fat. In this post, I’m going to show you how to refactor fat Laravel controllers using Responsable interface which was introduced in Laravel 5.5.
Why controllers become fat?
When writing controllers, I retrieve some data from database then format them before passing to blade template. Such a formatting logic would easily make a controller fat. Here is an example code.
<?php
class ProductController extends Controller
{
public function show($id)
{
$product = $this->products->findOrFail($id);
$data = [
'head' => $this->generateHeadTagsArray([
'title' => $product->search_engine_title,
'keywords' => $product->product_name,
'description' => $product->search_engine_description,
'ogp_type' => 'product',
'ogp_title' => $product->ogp_title,
'ogp_image' => $product->image_url,
'ogp_url' => 'https://example.com/products/' . $id,
'canonical_url' => 'https://example.com/products/' . $id
]),
'product' => $product,
];
return view('product.show', $data);
}
/**
* Generate head tags array.
*
* @param array $attributes
* @return array
*/
protected function generateHeadTagsArray($attributes)
{
// some formatting logic
}
}
In this example, I get product data from database, generate an array from the retrieved data and pass it to the blade template. Here I need to set proper meta tags in the HTML head depending on product data.
Responsable Interface
If you are in a similar situation, it's time to use Responsable interface! Responsable interface was introduced in Laravel 5.5. It allows you to convert objects to an HTTP response. A Responsable object has to implement toResponse() method which will be called by framework Router.
Now, let’s implement ProductResponse class which implements Responsable interface. You can pass any parameters to the constructor that are used to create a response. In the example below, I pass the product object to the constructor and format it in the toResponse()
method.
<?php
use Illuminate\Contracts\Support\Responsable;
class ProductResponse implements Responsable
{
/**
* Create new instances for dependencies.
*
* @param $product
*/
public function __construct($product)
{
$this->product = $this->product;
}
/**
* Create an HTTP response that represents the object.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function toResponse($request)
{
$data = [
'head' => $this->generateHeadTagsArray([
'title' => $this->product->search_engine_title,
'keywords' => $this->product->product_name,
'description' => $this->product->search_engine_description,
'ogp_type' => 'product',
'ogp_title' => $this->product->ogp_title,
'ogp_image' => $this->product->image_url,
'ogp_url' => 'https://example.com/products/' . $this->product->id,
'canonical_url' => 'https://example.com/products/' . $this->product->id
]),
'product' => $this->product
];
return view('product.show', $data);
}
/**
* Generate head tags array.
*
* @param array $attributes
* @return array
*/
protected function generateHeadTagsArray($attributes)
{
// some formatting logic
}
}
Now we can return a ProductResponse instance from the controller. We moved formatting logic to the ProductResponse and the controller is much simpler!
<?php
class ProductController extends Controller
{
public function show($id)
{
$product = $this->products->findOrFail($id);
return ProductResponse($product);
}
}
Matt Stauffer talked about Responsable interface at Laracon US 2018.
Happy refactoring!!