DEV Community

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

Avoid the Mess: Structuring Laravel View Data with ViewModels

Raheel Shan on August 28, 2025

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 wit...
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.

Collapse
 
developerkwame profile image
Oteng Kwame

I don't understand what's going on here

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

How does the $params get passed to the GetAllBrands::handle etc. And where are they defined?

Collapse
 
raheelshan profile image
Raheel Shan

They are supposed to be like this from controller.

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

Once done, pass down the params.

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

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