Refactor fat Laravel controllers using Responsable interface

Dec 6, 2018 Laravel

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!!

avatar

About Me

I am a software engineer from Japan and currently based in Singapore. Over the past 5 years, I've been creating e-commerce and FinTech web applications using Laravel, Angular, and Vue.js. Making clean, maintainable, and scalable software with agile methodology is one of my biggest passions. Im my spare time, I play the bass and enjoy DIY.