phplaraveldomain-driven-designlaravel-9model-view

Laravel Resources VS ViewModel


I'm starting to work on an API service with Laravel. Now, I'm using a DDD approach (and learning it at the same time).

Currently my structure looks like this:

MyApp

As you can see I'm currently using Resources. So for example, in my Categories' controller I've got:

public function index(): AnonymousResourceCollection
    {
        $categories = Category::all();
        return CategoriesResource::collection($categories);
    }

and my resource file looks like this:

<?php

namespace Domain\Category\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CategoriesResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  Request  $request
     * @return array
     */
    public function toArray($request): array
    {
        return [
            'id' => (string)$this->id,
            'type' => 'categories',
            'attributes' => [
                'name' => $this->name,
                'parent' => $this->parent,
                'description' => $this->description,
                'image' => $this->image,
                'created_at' => $this->created_at,
                'updated_at' => $this->updated_at,
            ]
        ];
    }
}

which returns the JSON response that will be eventually expected by the frontend.

Now, I've been reading about ModelView but still don't understand the concept or how could it work instead of (or along with) Resources. Most of the examples I've seen so far don't actually return a whole JSON response from the ModelView.

Thanks


Solution

  • They are 2 different things. Laravel Resources are especially design for API responses, to transform your Eloquent models into JSON responses using any kind of logic that you may need. As simple as that (Like you're doing in your code).


    TL;DR:

    Resource: Transform, add or remove attributes, load relationships, into a "JSON representation of your models (Or collection of models)".
    ModelView: It's just a file with a bunch of methods to be used in you view instead of passing each variable individually in every method of your controller.


    Long explanation:

    Now, in the link you posted about ModelView they give this examples as the ordinary way of passing data to a view:

    public function create()
    {
        // Here we are just passing the $categories
        return view('blog.form', [
            'categories' => Category::allowedForUser(
               current_user()
            )->get()
        ]);
    }
    
    public function edit(Post $post)
    {
        // Here we are passing $post, and the $categories are
        // the same like in the "create" method
        return view('blog.form', [
            'post' => $post,
            'categories' => Category::allowedForUser(
               current_user()
            )->get()
        ]);
    }
    

    This "ViewModel" is just a way to encapsulate all of the logic and methods away from your controllers into a single file, which at the end, contains all the information passed to the view (Even data that you may not need).

    class PostFormViewModel
    {
        public function __construct(User $user, Post $post = null) 
        {
            $this->user = $user;
            $this->post = $post;
        }
        
        public function post(): Post
        {
            return $this->post ?? new Post();
        }
        
        public function categories(): Collection
        {
            // The same query as in the "ordinary" example
            return Category::allowedForUser($this->user)->get();
        }
    }
    
    class PostsController
    {
        public function create()
        {
            $viewModel = new PostFormViewModel(
                current_user()
            );
            
            return view('blog.form', compact('viewModel'));
        }
        
        public function edit(Post $post)
        {
            $viewModel = new PostFormViewModel(
                current_user(), 
                $post
            );
        
            return view('blog.form', compact('viewModel'));
        }
    }
    

    And this, is how should be use in the view:

    <input value="{{ $viewModel->post()->title }}" />
    <input value="{{ $viewModel->post()->body }}" />
    
    <select>
        @foreach ($viewModel->categories() as $category)
            <option value="{{ $category->id }}">
                {{ $category->name }}
            </option>
        @endforeach
    </select>
    

    There are a few things I don't like about this approach (In this particular example):