David Llop

Back-End Developer

Written on

Adding View models to a Laravel project

Since the beggining of 2017 I started to experiment with different kind of codes and patterns I didn't used before.

One of that changes came in November when I was developing the code of this other blog. I saw that I was duplicating a lot of code inside my controllers on the methods for create and edit a resource, in which I use the same blade file.

Some data of that view depends on the model I'm editing or creating. For example, if it's an existing record the title should be Edit post: {$post->title} instead of Create a post. That logic applies to the form action and form method too.

I created a main FormViewModel, and every view model dedicated to a form extends it.

abstract class FormViewModel
{

    public $model;
    public $title;
    public $action;
    public $method;

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

        $this->setTitle();

        $this->setAction();

        $this->setMethod();
    }
}

This view models are forced to declare the methods called on the constructor

protected abstract function setTitle();

protected abstract function setAction();

protected abstract function setMethod();

This is an implementation of the setTitle method in the CategoryDetailViewModel:

protected function setTitle()
{
    $this->title = $this->model->exists
        ? "Edit Category: {$this->model->name}"
        : "Create Category";
}

They can also add more logic in their own constructor, like the PostDetailViewModel:

public function __construct(Post $post)
{
    parent::__construct($post);

    $this->published_at = $this->model->exists
        ? $this->model->published_at->format('Y-m-d\TH:i')
        : Carbon::now()->format('Y-m-d\TH:i');

    $this->categories = Category::select('id', 'name')->get();
}

Using the view models is very easy, the controller only needs to pass this object to the view

public function edit(Theme $theme)
{
    return view('backend.themes.edit', [
        'view' => new ThemeDetailViewModel($theme)
    ]);
}

and the view use the object properties and methods

@section('content')
    <h1>{{ $view->title }}</h1>
    
    <form method="POST" action="{{ $view->action }}">
        {{ method_field($view->method) }}
        {{ csrf_field() }}
        ...
    </form>
@endsection

Spotted a mistake? Noticed something to improve? Feel free to edit this post on GitHub.


Special thanks 🙏 to Dieter Stinglhamber for the amazing code of this blog