Skip to main content

Laravel Viewmodels

·2 mins

The problem #

When we start programming in Laravel it’s common for us to overload our controllers with many responsibilities.

something more or less like this:

// MerchantController.php

public function create(): View
{
    return view('admin.merchants.create', [
        'countries' => Country::all(),
        'currencies' => Currency::all()
    ]);
}

If we add business requirements to this (ACLs, scopes, etc) we could end up with a huge controller that will make it very difficult to maintain in the future.

What is a ViewModel? #

ViewModels are a kind of pattern that allows you to encapsulate all the logic needed to prepare the data thats will be used by the view.

Let’s do it! #

First, we are going to create an abstract class named ViewModel

<?php
// ViewModel.php

namespace App\ViewModels;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Model;

abstract class ViewModel implements Arrayable
{
    protected Model $model;

    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    abstract public function toArray(): array;

    protected function model(): Model
    {
        return $this->model;
    }
}

Next, we are going to create our concrete implementation

// CreateViewModel.php

<?php
namespace App\ViewModels\Admin\Merchants;

use App\Models\Merchant;
use App\Models\Country;
use App\Models\Currency;
use App\Models\MerchantCategoryCode;
use App\ViewModels\ViewModel;

class CreateViewModel extends ViewModel
{
    public function __construct()
    {
        parent::__construct(new Merchant());
    }
    
    public function toArray(): array
    {
        return [
            'countries' => Country::all(),
            'currencies' => Currency::all(),
            'mccs' => MerchantCategoryCode:all(),
            'merchant' => $this->model()
        ];
    }
}

Finally, we refactor the Controller to use our ViewModel.

// PostController.php

<?php
namespace App\Http\Controllers\Admin;
use App\View\ViewModels\Admin\Merchants\CreateViewModel;
use Illuminate\Http\Response;
class PostController extends Controller
{
    public function create(): Response
    {
        return view('admin.merchants.create', new CreateViewModel());
    }
}

You can view a ViewModel implementation at this link on Github