DEV Community

Cover image for Avoid the Mess: Structuring Laravel View Data with ViewModels
Raheel Shan
Raheel Shan

Posted on • Edited on • Originally published at raheelshan.com

Avoid the Mess: Structuring Laravel View Data with ViewModels

When working with .NET Framework MVC applications, I used to use bound view models, which let me use classes on the Razor templates very easily with the autocomplete feature. This was a default feature provided for .NET Framework by Visual Studio IDE. In the Razor views, we had to define a class as a model on top, and on the other hand, in the controller, we were forced to pass the instance of that class to the view. If passed a different class instance, it would throw an error telling this view required the instance of the defined class. Once this was done, we were able to access class with a variable called Model. This gave the autocomplete feature within Razor view.

I was looking for the same thing in the Laravel ecosystem, but I found none. So I practiced a pattern I call ViewModels and kept using it for many years. What is ViewModel in Laravel? Let me explain.

What is ViewModel?

In my view, a ViewModel is a class that fulfills the requirements of a page.

To further explain, let’s assume a scenario. Say we have to display a list of products with some filters above. The filters can be a list of categories and brands in dropdowns. Users can select a category and apply a filter to fetch products of a specific category. Users can do the same with brands. Thus, the ViewModel for the page will look something like this.

Defining ViewModel Class

<?php

namespace App\ViewModels;

class ProductsViewModel {
    public $products;
    public $categories;
    public $brands;
}
Enter fullscreen mode Exit fullscreen mode

So this class will have 3 properties to hold the required data for categories, brands, and products.

Let’s assume another example. This time we need a ViewModel for the e-commerce home page. The home page will have banners and some category sections.

class ProductsViewModel {
    public $banners;
    public $sections = [];
}
Enter fullscreen mode Exit fullscreen mode

Each section in the array can have the following:

class Section {
    public $title;
    public $products = [];
}
Enter fullscreen mode Exit fullscreen mode

Filling ViewModel with Data (Using AQC)

Now let’s move forward. We have defined the ViewModel class. Now we need to fill it with data to view. I am going to use the AQC design pattern here to get data. If you haven’t heard about it, you can read my post here.

Introducing the Atomic Query Construction (AQC) Design Pattern

namespace App\ViewModels;

use App\AQC\Products\GetAllProducts;
use App\AQC\Category\GetAllCategories;
use App\AQC\Brand\GetAllBrands;


class ProductsViewModel {
    public $products;
    public $categories;
    public $brands;

    public function handle($params)
    {
        $this->brands = GetAllBrands::handle();
        $this->categories = GetAllCategories::handle();
        $this->products = GetAllProducts::handle();
        return $this;
    }
}
Enter fullscreen mode Exit fullscreen mode

And we are done. Now let’s move towards the controller and see how we use it.

Using ViewModels in Controllers

<?php

namespace App\Http\Controllers;

use App\Http\Requests\GetAllProductsRequest;
use App\ViewModels\ProductsViewModel;
use App\Helpers\ResponseHelper;

class ProductController extends Controller
{
    public function index(GetAllProductsRequest $request)
    {
        $params = $request->all();
        $productsViewModel = new ProductsViewModel();
        $response = $productsViewModel->handle($params);
        return ResponseHelper::handle('index', [ 'model' => $response ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

If we follow this approach, all our Laravel Blade templates will have only one data access point, and that is $model.

<h1>{{ $model->product->title }}</h1>
Enter fullscreen mode Exit fullscreen mode

Comparing Controller Approach

Using the ViewModel, we are passing a single data key to view. If we aren’t using ViewModel, our code looks like this.

<?php

namespace App\Http\Controllers;

use App\Http\Requests\GetAllProductsRequest;
use App\AQC\Products\GetAllProducts;
use App\AQC\Category\GetAllCategories;
use App\AQC\Brand\GetAllBrands;

class ProductController extends Controller
{
    public function index(GetAllProductsRequest $request)
    {
        $params = $request->all();
        $brands = GetAllBrands::handle($params);
        $categories = GetAllCategories::handle($params);
        $products = GetAllProducts::handle($params);

        $data = [
            'brands' => $brands,
            'categories' => $categories,
            'products' => $products
        ];

        return view('index', $data);
    }
}
Enter fullscreen mode Exit fullscreen mode

You can see instead of passing a single data key model, we are now passing much more data to view, which we will have to remember when accessing in the blade template. This is only a sample. Imagine your page required much more data, so your controller will get fat.

By moving all the required data to a ViewModel class, our controller is lean and our logic is simple.

What Are We Lacking?

Even though we have simplified our controller, many IDEs currently lack the autocomplete feature in blade templates. But gluing the required data to a class and passing it as a model to view is still a good improvement.

In the Laravel ecosystem so far, I have found this package by Spatie, which does the same, but I like my own implementation. If you want to take a look, here it is.

GitHub - spatie/laravel-view-models: View models in Laravel


If you found this post helpful, consider supporting my work — it means a lot.

Support my work

Top comments (10)

Collapse
 
xwero profile image
david duymelinck

I think the ViewModel concept is very tricky to get right, because it is easy to cause SRP violations or separation of concerns violations.
In an ideal situation to create a view, there is a storage query combiner to reduce the number of queries and a data transformer to prepare the data from the storage that is still in a raw form. These mechanisms should be configurable to make them as maintainable as possible.

Moving from an array to a class has not much added value.
If you want a root for the view data just wrap the keys, return view('index', ['page' => $data]);.
The reason an array is the most common data structure for views is because views are prone to change. Having a set schema makes it harder to make changes.
If you want more control over the array keys you could use enums.

For me the ViewModel is an abstraction that will hurt you in the long run. If you look at the Spatie controller example I already see problems when the create and edit pages will not contain the same fields.

Collapse
 
raheelshan profile image
Raheel Shan • Edited

You may be right but I disagree. I am going to post a 5-post series on this topic next. This approach will also give us autocompletion in blade views which is not possible with arrays. Moreover, I am going to introduce fully-typed blade views and the blade views will always expect specific type of data. If not provided or provided different data, they will complain. The main purpose is to follow strict structure and set a standard for the whole team to follow.

Collapse
 
xwero profile image
david duymelinck

While I agree creating a DTO like object makes it easier to autocomplete and allows to check the type. I would go all out with the DTO concept instead of having a mix of storage retrieval and a DTO.

Thread Thread
 
raheelshan profile image
Raheel Shan • Edited

Yes DTO is an excellent option. I have thought it through. ViewModels can have properties have DTO type classes. Viewmodels in my views are about defining a strict type shape the views can depend on. Moreover if you have used graphql that is initiator for the required data, here viewmodels will act same and ask for data required by view.

Thread Thread
 
xwero profile image
david duymelinck

The main reason I don't like the ViewModel is the data retrieval, because you're adding the whole class to the view. Nothing is stopping you to do the data retrieval in the view, and that could be a source of problems.
if you look at the Spatie example they also have no data retrieval. The model, Post is added during instantiation.

About the shape, you can do that with DTOs too.

I think graphql is a bad comparison because it is a an independent layer, while the ViewModel is a part of the view.

Thread Thread
 
raheelshan profile image
Raheel Shan • Edited

I should clarify that in my approach, the ViewModel isn’t responsible for fetching data you can do it anyhow. Retrieval stays in services, repositories, or wherever your domain logic lives. The ViewModel’s job begins after the data exists — it simply provides a typed, predictable contract for the view. That’s the same reason the Spatie example avoids retrieval inside the ViewModel, and I agree with that boundary.
On shape, DTOs can do this as well, but I find that tying the contract directly to the view makes the intent clearer for teams. DTOs are more general-purpose, while ViewModels are focused on ensuring the Blade side always receives exactly what it expects.
And about GraphQL, I only bring it up as a conceptual analogy. Both enforce a strict definition of "this is the data a view will get". The implementation context is different, but the discipline of shaping data is the same.

Thread Thread
 
xwero profile image
david duymelinck

In the handle function you call data retrieval methods, so you are contradicting your own boundary.

A DTO is what you want it to be, it can be general purpose but it can also be as specific as you want it to be. The first examples of the ViewModel classes are DTOs.
A way to structure it to make the DTOs focused is with a ViewData suffix for the classes or group them in a ViewData directory.

You are just changing the graphql concept to fit your narrative. In the first comment you mention it asks for the data, and with the latest comment you mention it is to shape the data.
This makes you look like you didn't really think it through.

Thread Thread
 
raheelshan profile image
Raheel Shan • Edited

The handle() in my examples isn’t about querying storage — it just assembles data that’s already been fetched into a strict shape for the view. I’ll make that clearer going forward.

On DTOs, the distinction I’m drawing is intent. A PostViewModel communicates, this is exactly what the post Blade template will always receive. It’s a narrower, view-focused contract, even if it looks similar to a DTO.

As of GraphQL, it both initiates requests and specifies only the fields needed. That’s the mindset I want ViewModels to bring to Blade, clear, minimal and predictable data.

Thread Thread
 
xwero profile image
david duymelinck

The section of the post where the handle example is named; filling the viewmodel with data. And you are using your design pattern that is just a layer that wraps the builder methods from Laravels Model class. That for me is a tight coupling with the data storage.
So in this post you are showing how to abuse the pattern, to show it the right way in further posts? That seems a bad way to introduce a pattern.

I also think the focus on Blade templates receiving the correct information is the wrong one. It is the job of the backend to send the right data to the templates. It should not matter what template engine you are using.

I think the reason for the ViewModel is to have the two mechanisms I mentioned in my first comment or at least the data transformer. But because they are not separated I see potential problems in the long run.

Thread Thread
 
raheelshan profile image
Raheel Shan • Edited

Let me simplify what I mean by a ViewModel.

class ProductsViewModel {
    public $products;
    public $categories;
    public $brands;
}
Enter fullscreen mode Exit fullscreen mode

That’s the core idea. Forget the handle method if it’s distracting — fetch the data however you want. The point isn’t how you retrieve it, but that the view always gets a clearly defined, consistent contract.

class ProductViewModel {
        public $product;
}
Enter fullscreen mode Exit fullscreen mode

or alternately

class Product extends Eloquent{

}
Enter fullscreen mode Exit fullscreen mode

or

class ProductDTO{
    public $name;
    public $price;
}
Enter fullscreen mode Exit fullscreen mode

You could call this a DTO if you want, but when it’s bound directly to a view I find “ViewModel” communicates intent better. Yes, the backend is responsible for providing data, but the ViewModel draws the line: this is exactly how much and what kind of data the view needs. Also in my first example $products can be of type ProductDTO.