02 Dec 2018
cat: Laravel, PHP
0 Comments

Laravel View-models

Introduction

In this tutorial we will make a base view-model to easily map request body to the view-model’s properties. View-models helps you present data to your views or response that cannot be presented with your existing models and it also helps clean up your controller from view logic. You could also transfer the validation logic from your controller to your view-models.

The disadvantages of view-models is that, in complicated models, you will have to create a lot PHP classes. This is quite time consuming or tiresome when you are creating them but it helps with maintenance in the long run.

Update: Inluded a gist at the bottom.

Base View Model

<?php

namespace App\Http\ViewModels;

abstract class ViewModel
{
    protected $mappings = [];

    /**
     * Map an array to the view-model's properties.
     *
     * @param $values
     * @return ViewModel
     */
    public function mapArray($values)
    {
        foreach ($this->mappings as $index => $mapping) {
            if (is_array($mapping)) {
                if (count($mapping) == 3 && $mapping[2] == 'array') {
                    foreach ($values[$mapping[1]] as $index2 => $value) {
                        /* @var $vm ViewModel */
                        $vm = new $mapping[0]();
                        $this->$index[] = $vm->mapArray($value);
                    }
                } else {
                    /* @var $vm ViewModel */
                    $vm = new $mapping[0]();
                    $this->$index = $vm->mapArray($values[$mapping[1]]);
                }
            } else {
                $this->$index = $values[$mapping];
            }
        }
        return $this;
    }

}

So this method will map the request body to the view-models’ properties. If you are using Laravel, you can just use $request->all() to get the request body. It can also map child view-models. Let us look at an example request.

{
    "first_name": "John",
    "last_name": "Doe",
    "items": [
        {
            "id": 1,
            "quantity": 2
        },
        {
            "id": 2,
            "quantity": 4
        }	
    ],
    "address": {
        "country": "test country",
        "city": "test city",
        "address1": "Test address",
        "state": "test state",
        "postcode": "1234"
    }
}

This is a typical request body. items is an array of objects and `address` is an object. Next, let us create the view-models for this specific request.

View-models

<?php

namespace App\Http\ViewModels;

class OrderViewModel extends ViewModel
{
    protected $mappings = [
        'firstName' => 'first_name',
        'lastName' => 'last_name',
        'items' => [Item::class, 'items', 'array'],
        'address' => [Address::class, 'address'],
    ];

    public $firstName;
    public $lastName;
    public $items;
    public $address;
}

Update: We can also use PHP magic methods __get and __set so we dont have to declate the properties such as $firstName and $lastName.

<?php

namespace App\ViewModels;

abstract class ViewModel
{

    // Removed for brevity.

    function __get($name)
    {
        if (empty($this->$name)) {
            return null;
        }

        return $this->$name;
    }

    function __set($name, $value)
    {
        $this->$name = $value;
    }

}

For the view-model to know what request body properties belongs to a view-models’ property, we override the $mappings property. To map an array we assign it an array of information in the format of [Class name, response body property, 'array'] and to map a regular object, we assign it an array in the format of [Class name, response body property].

We need to make the other two view-model classes.

<?php

namespace App\Http\ViewModels;

class Item extends ViewModel
{
    protected $mappings = [
        'id' => 'id',
        'quantity' => 'quantity'
    ];

    public $id;
    public $quantity;
}
<?php

namespace App\Http\ViewModels;

class Address extends ViewModel
{

    protected $mappings = [
        'country' => 'country',
        'city' => 'city',
        'address1' => 'address1',
        'state' => 'state',
        'postCode' => 'postcode'
    ];

    public $country;
    public $city;
    public $address1;
    public $state;
    public $postCode;

}

We can now finally use the view-models in our controller action.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\ViewModels\OrderViewModel;

class OrderController extends Controller
{

    public function store(Request $request)
    {
        $viewModel = new OrderViewModel();
        $viewModel->mapArray($request->all());

        dd($viewModel);
    }

}

If we try this on postman we will get something like this.

Laravel View-models

That is it for this tutorial. Thanks for reading.

References

Be the first to write a comment.

Post A Comment