I’ve been tinkering with a new way of returning various response formats by introducing dedicated response objects to my Laravel web applications. This has been heavily inspired (read: copy & paste) by DHH and Adam Wathan’s chats on the Full Stack Radio Podcast and I thought I’d share my journey through, and ideas on, it with you.
Within my application I generally, if not always, approach my controllers from a CRUD only perspective. A
BeanController would provide the standard CRUD controller methods:
destroy. Each of these methods would return a response suitable for my web interface, i.e., a view or a redirect.
Here is a bare bones example of my general structure and approach:
Nothing out of the ordinary there.
Single action controller
Then along comes a request to download all
Bean’s as a CSV file. Well that isn’t a big deal - what I’ll do is create a dedicated “single action” controller
BeanCSVExportController to handle it for me.
I felt it was a good idea to have another controller handle the CSV export directly, as I didn’t like depending on / injecting a
CsvWriter in my main
[email protected] method when 99% of requests are for a web interface response. It didn’t feel right.
So I’d end up with a second controller to handle this scenario which would receive the
CsvWriter as a method dependency:
And for a time - things were good.
But I started to think it was a bit strange using the
__invoke() method. When I took a step back it looked to me like an
index() call. After all I am showing a list of users…just in a different format…right?!? 🤔
So I started moving these over to use
index() instead of
__invoke(). But still - if it is single action - why would you specify what the action is?
I’m also going to be adding another controller for each new format: XML, JSON, etc.
Something just was not right either way I looked at it.
Seeing the light
Then it hit me; that I was really doing the same thing with my
[email protected] and
[email protected]. Pulling the
Beans out of the database and pushing them to a response. I knew I had to find a simple solution to combine these controllers but still offer the different response formats.
This all became crystal clear when I started adding filters into the mix. When both my
index() methods were adding query scopes based on HTTP query parameters - I instantly saw the duplicate code and knew there had to be a better way as they both would always start exactly the same.
I could have refactored the query building out to a Repository - but I already felt the controllers needed to be merged - so that solution didn’t make sense to me.
Ah-hah! Response objects
I listened to DHH and Adam talking about dealing with different response formats by looking at the
Accepts header. I saw Adam tweet that he had put together a macro to help with this kinda thing. In addition to this, I also saw the
Responsable interface Laravel provided.
It all looked pretty neat and the cogs started turning, but I was busy on other projects and didn’t have time to play and work out a better solution.
I also found myself starting to build more API first approaches to web systems and the whole time I’m thinking: if I needed to introduce a web interface here, I’m again going to be duplicating these controllers: there. must. be. a. better. way!
I finally got some time to come back to the multi response format idea on a project and look at some ways to clean this all up using a new approach. However, since that Twitter post there was another (awesome) episode of the Full Stack Radio where Adam and DHH also discussed the idea of listening to file extensions and why that is valid - and I think that was the ah-hah moment for me.
This is where dedicated response objects rock!
Response objects that implement the
Responsable interface can be returned from a controller and Laravel will call the
toResponse($request) method on it. This allows you to move any complexity you might have creating a response out of the controller and into a dedicated object. They are really nice.
One controller to rule them all
I had a play with response objects and built a base
Responsable class that would determine the response format (HTML, JSON, CSV, etc) based on either the url file extension or the
If a HTML response was expected, the
toHtmlResponse() method would be called on the object, if JSON was the preferred response format, the
toJsonResponse() method would be called, and so on. This allowed me to break up the logic required to create format specific responses into their own methods.
This was just the solution I was looking for to combine my controllers. Suddenly I’m cleaning things up and everything is starting to click. I can pipe all responses that need to list
Bean’s through the
[email protected] method. I can share the filtering across all response formats and defer the creation of the actual response to a dedicated object. Combining both the HTML and CSV controller resulted in a really streamlined controller:
Within my response object I can now decide how I want things to happen for each response format and other formats can be added as needed. If we need to add a JSON API endpoint - that is easy: leave the controller as is and add a
toJsonResponse() method to the response object. Then our JSON endpoint will inherit all the filtering abilities shared amongst the other response formats for free.
Here is a response object that extends my base
Responsable class that will return our HTML, CSV, and JSON for our Beans:
With the correct routes setup, my
[email protected] method can now respond with the correct format to these following urls. All query parameter filters would work accross them all!
// html view GET: /beans // csv export GET: /beans.csv // json api GET: /beans.json
This pattern really has no use in a single response format situation - but once you need to provide multiple response formats - it works really well to clean up your controllers and move the response logic to its own home within your application.
I am really loving this new pattern and it has been the kind of thing, for me, where I want to go back and re-write everything right now instead of waiting until I touch the code again to update it.
After I tweeted about this Adam was great enough to reply with what he settled on for this kind of thing. I like the approach he has taken. It is simple and gets the job done. I would never have gotten to my own solution without seeing his approaches. I also saw that Miguel Piedrafita has packaged it up for others to use.
I personally like the dedicated class, as I can:
- Resolve method dependencies from the container for the individual formats
- Recursively check for, and resolve, returned
I also feel that it is more readable when dealing with multiple formats that each might do a bit of work - this is all totally subjective of course, and these things, I’m sure, could also be added to the macro.
Either way I think it is a great pattern. I am really looking forward to implementing it, while both cleaning up and combining some of my controllers! Hopefully some of this might be new to you as well and you can give it a whirl in your own applications. If you have any thoughts or your own implementations of this kind of thing I’d love to see it and learn more.
If you are interested in the base class I’ve been using I made a gist where you can check it out. If you have any suggestions on improvements I’d love to hear them.