Code Monkey home page Code Monkey logo

laravel-view-models's Introduction

View models in Laravel

Latest Version on Packagist GitHub Workflow Status Total Downloads

Have you ever made a controller where you had to do a lot of work to prepare variables to be passed to a view? You can move that kind of work to a so called view model. In essence, view models are simple classes that take some data, and transform it into something usable for the view.

You'll find a more detailed explanation and some good examples in this blogpost on Stitcher.io.

Laravel's native view composers are not the same as the view models provided by this package. To learn more about the differences head over to this blogpost on Stitcher.io.

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Installation

You can install the package via composer:

composer require spatie/laravel-view-models

Usage

A view model is a class where you can put some complex logic for your views. This will make your controllers a bit lighter. You can create a view model by extending the provided Spatie\ViewModels\ViewModel.

class PostViewModel extends ViewModel
{
    public $user;

    public $post;
    
    public $indexUrl = null;

    public function __construct(User $user, Post $post = null)
    {
        $this->user = $user;
        $this->post = $post;
        
        $this->indexUrl = action([PostsController::class, 'index']); 
    }
    
    public function post(): Post
    {
        return $this->post ?? new Post();
    }
    
    public function categories(): Collection
    {
        return Category::canBeUsedBy($this->user)->get();
    }
}

Then you can use the view model class in your controller like this:

class PostsController
{
    public function create()
    {
        $viewModel = new PostViewModel(
            current_user()
        );
        
        return view('blog.form', $viewModel);
    }
    
    public function edit(Post $post)
    {
        $viewModel = new PostViewModel(
            current_user(), 
            $post
        );
    
        return view('blog.form', $viewModel);
    }
}

In a view you can do this:

<input type="text" value="{{ $post->title }}" />
<input type="text" value="{{ $post->body }}" />

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

<a href="{{ $indexUrl }}">Back</a>

All public methods and properties in a view model are automatically exposed to the view. If you don't want a specific method to be available in your view, you can ignore it.

class PostViewModel extends ViewModel
{
    protected $ignore = ['ignoredMethod'];

    // …
    
    public function ignoredMethod() { /* … */ }
}

All PHP's built in magic methods are ignored automatically.

View models as responses

It's possible to directly return a view model from a controller. By default, a JSON response with the data is returned.

class PostsController
{
    public function update(Request $request, Post $post)
    {
        // …
        
        return new PostViewModel($post);
    }
}

This approach can be useful when working with AJAX submitted forms.

It's also possible to return a view directly:

class PostsController
{
    public function update(Request $request, Post $post)
    {
        // …
        
        return (new PostViewModel($post))->view('post.form');
    }
}

Note that when the Content-Type header of the request is set to JSON, this approach will also return JSON data instead of a rendered view.

Exposing view functions

View models can expose functions which require extra parameters.

class PostViewModel extends ViewModel
{
    public function formatDate(Carbon $date): string
    {
        return $date->format('Y-m-d');
    }
}

You can use these functions in the view like so:

{{ $formatDate($post->created_at) }}

Making a new view model

The package included an artisan command to create a new view model.

php artisan make:view-model HomepageViewModel

This view model will have the App\ViewModels namespace and will be saved in app/ViewModels.

or into a custom namespace, say, App\Blog

php artisan make:view-model "Blog/PostsViewModel"

This view model will have the App\Blog\ViewModels namespace and will be saved in app/Blog/ViewModels.

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you've found a bug regarding security please mail [email protected] instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

laravel-view-models's People

Contributors

adrianmrn avatar alexbowers avatar alexmanase avatar bastien-phi avatar brendt avatar clovon avatar erikn69 avatar freekmurze avatar gjinali avatar ijpatricio avatar jamesfreeman avatar jdrieghe avatar kadimi avatar laravel-shift avatar mattdfloyd avatar maxgiting avatar patinthehat avatar shuvroroy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-view-models's Issues

Changes in my view model are not showing

My new added code also edited code are not updating in my webpage.
Here is my view model function :

public function movie()
    {
    	return collect($this->movie)->merge([
    			'poster_path' => 'https://image.tmdb.org/t/p/w500/'. $this->movie['poster_path'],
    			'release_date' => \Carbon\Carbon::parse($this->movie['release_date'])->format('M d, Y'),
    			'genres' => collect($this->movie['genres'])->pluck('name')->flatten()->implode(', '),
    			'runtime' => date('g\h i',mktime(0,$this->movie['runtime'])),
    			'crew' => collect($this->movie['credits']['crew'])->take(2),
    			'cast' => collect($this->movie['credits']['cast'])->take(5),
    			'backdrops' => collect($this->movie['images']['backdrops'])->take(3),
    			'release_year' => substr($this->movie['release_date'],0,4),
                        'reviews' => collect($this->movie['reviews']['results'])->take(5),
                        'author' => collect($this->movie['reviews']['results'][0]['author_details']),
    		])->dump();
    }

controller :

public function show($id)
    {

        $movie = Http::withToken(config('services.tmdb.token'))
            ->get('https://api.themoviedb.org/3/movie/'.$id.'?append_to_response=credits,videos,images,reviews')
            ->json();

        if ($movie['imdb_id'] != "") {
            $imdb = Http::get('http://www.omdbapi.com/?i='.$movie['imdb_id'].'&apikey='.$apikey)
            ->Json();
        }
        else {
            $imdb = $movie;
        } 
        //dump($movie);
        $viewModel = new MovieViewModel(
            $movie, 
            $imdb,
        );
        return view('movie.show', $viewModel);
    }

Here I have added the 'review' and 'author' field but not getting it. I also changed 'backdrops' field to take(3) from take(9) but no change. Here is what I am getting :
https://ibb.co/w69BRQS

Nested ViewModel doesn't get created under the ViewModels namespace

When using the command php artisan make:view-model Game/IndexGamesViewModel a view model gets create under the namespace App\Game instead of App\ViewModels\Game as expected (e.g. compare the make:controller command).

As per https://github.com/spatie/laravel-view-models/blob/main/tests/ViewModelMakeCommandTest.php it is the desired behaviour but what about synchronizing it to other Laravel behaviours?

I'd be willing to contribute this enhancement.

Simplify toResponse method on ViewModel

Hey, I am not quite sure but I think the toResponse method on ViewModel.php can be simplified.
The original method looks like this:

public function toResponse($request): Response
{
    if ($request->wantsJson()) {
        return new JsonResponse($this->items());
    }
    
    if ($this->view) {
        return response()->view($this->view, $this);
    }
    
    return new JsonResponse($this->items());
}

I think you can remove the first if statement so the method looks like this:

public function toResponse($request): Response
{
    if ($this->view) {
        return response()->view($this->view, $this);
    }

    return new JsonResponse($this->items());
}

Running into a method name conflict with the "items" method in ViewModel.php

A suggestion...

It may be beneficial to rename the "items" method within "ViewModel.php" to something a bit more obscure.

I ran into a conflict today where I wanted to use $items within my blade template, but I obviously could not redeclare the "items" method in my ViewModel.

I worked around the issue by making $items a public property on my ViewModel and setting its value within the constructor, but I don't think this would be the best solution.

Thanks for the great package!

Lazily initialize view model methods

An issue I run into a lot with view models (especially in combination with BladeX components) is that all view model methods are called/initialized when the view it's applied to is loaded. When you have an if statement wrapping a section that contains other view model properties for instance, all properties are called ahead of that if statement, meaning you have to handle all if/else situations for each property:

@if($isAccessible)

    {{ $conditionalValue }}

@endif

Non-working example:

<?php

declare(strict_types=1);

class ComponentViewModel extends ViewModel
{
    /**
     * @return bool
     */
    public function isAccessible() : bool
    {
        return false;
    }

    /**
     * @return string
     */
    public function conditionalValue() : string
    {
        return 'e.g. a query that can only be run when $isAccessible is true';
    }
}

In order to make this work, you have to do every wrapping check in each property's method:

<?php

declare(strict_types=1);

class ComponentViewModel extends ViewModel
{
    /**
     * @return bool
     */
    public function isAccessible() : bool
    {
        return false;
    }

    /**
     * @return string
     */
    public function conditionalValue() : string
    {
        if (! $this->isAccessible()) {
            return '';
        }

        return 'e.g. a query that can only be run when $isAccessible is true';
    }
}

Is there any way to extend or refactor the package to support these kind of conditionals, or is it part of its core functionality that the view model methods are "eagerly initialized" when the view is used? Say, some sort of lazy loading of view model methods/properties.

using repositories/dependencies?

so previously i had code like this in my controller: (simplified, lots more repos)

$data = [
            'locations' => $locationRepository->all()
];

I am having trouble moving the repo to the view Modal (VM)?

if i add these dep into VM's __construct then i'll have to manually add them when i call this view modal..

if i add it to the function getLocations() on the VM then i still need to pass in the repo when calling getLocations.. or instantiate a new Repo inside getLocations()

the last solution looks ok - but was wondering on any suggestions?

at the moment i am trying this:

$viewModel = new AssignmentViewModel($request, $tutorRepository, $studentRepository, $locationRepository);
class AssignmentViewModel extends ViewModel
{
    public ... 

    public function __construct(
        Request $request,
        TutorRepository $tutorRepository,
        StudentRepository $studentRepository,
        LocationRepository $locationRepository
    )

and creating all variables in construct instead of having their own functions.. :/ e.g for locations or students etc

A question

Man, sorry to ask u that but, what are the differences between this package and View Composers?

[Support] How to reuse view model

I have 2 view models: ProductsViewModel for products page and ProductViewModel for product detail page.

In ProductsViewModel, I already have a function permalinkCategory to create a URL to product category or featuredImage function to generate the product's thumbnail.

How can I reuse those function without rewrite functions in ProductViewModel? Should I combine 2 above view models in each controller and return to the view?

Thank you.

How does it proxies getXxxAttribute methods?

I have lots of getXxxAttribute methods on some of my models, because I find this feature very convenient (with its automatic caching). Will those methods or properties (they make) be available in view model?

Every public method is called on initialization

Is that correct behaviour that each public method from viewMethod is called when I pass view model to blade template even if I don't use it there?

My controller

$viewModel = new AgendaViewModel($event, $agenda);
return $this->view('periodicity', $viewModel);

periodicity is an empty view - there is nothing inside there

AgendaViewModel

public function __construct(Event $event, Agenda $agenda = null)
{
    $this->event = $event;
    $this->agenda = $agenda;
}

public function singleAgenda()
{
        dump('1');
        return [];
}

public function agendas()
{
        dump('2');
        return [];
}

When viewModel is passed to the view each public method is called and I can see two dumps. However I didn't use these methods anywhere.
Is such behaviour a bug or feature?

How can I use this package with Collection of a Model ?

In my index method of the Controller I fetch all users and I like to pass them to the view to allow for replacing some status code with text strings

    public function index()
    {
        $users = Users::all();

        return view('users.index', compact('users')); // <- can't use with ModelView as it expecting A User Model.
    }

Should I create a method which get the collection and convert each item to the view model object and pass that to the view ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.