Can I bind public Eloquent Model properties?

I am little lost - what the 27 Dec release exactly provided in the terms of:
Enable setting Eloquent models and collections of models as public properties.

Can I do wire:model="product.title" or wire:click="$toggle('product.status')"?

So far, I am preparing property values for the model biding in my mount($id) method:

$this->product = Product::find($id);
$this->title = $this->product->title;
$this->price = $this->product->price;

And having public properties: $title, $price

If it’s not possible to bind data to model properties like wire:model="product.price", can you help me understand what’s so great about this 27 Dec release that brought “Model serialization to public properties”, please?

Anyone please feel free to correct or add on to this. For future readers, this is based on 0.7.0, and things could change.

The short answer: No, your model is not directly accessible in javascript, and properties cannot be bound.

Medium Answer:

Well, it is, kind of, but not in the way you want to use it; not in a way that you can bind the properties to inputs (more on that later). You can however ->toArray() it to a different public property and bind the properties with dot notation so you don’t end up with such a monster of a component defining a public property for each thing you want to bind. Syncing them back together, validation, and authorization is still on you.

Long Answer:

First, you have to understand the lifecycle of a livewire request. The quick and dirty of it is public properties and values are passed into javascript at the end of the request, user does something that changes data, and then they are passed back to php and reassigned at the beginning of each request. This is how livewire emulates state. Protected properties are not passed, and their state is not persisted.

Before the update, public property’s could only be data types that can be used by javascript, which model objects are not, so you had to assign models to a protected property. Also there would be a huge security opening by exposing the full contents of your model to javascript, so that alone is reason enough to not allow them to be public. Since that model wasn’t being sent back and forth between the back and front, it had to be saved somewhere to keep it’s state to be usable again on the next request, so protected properties were being saved to the cache.

Cool, problem solved right? Well not exactly. When you combine that:

  1. State is being stored in 2 spots,
  2. How the cache was being maintained to keep it manageable,
  3. Actions a user could do.

A certain mixture of the three would cause public and private properties to become out of sync, and things would break.

If you have worked with laravel’s queues, this is how it’s handled in the background when you pass a model to your queue. This also brings us to what is put into javascript from your model:

  • The model name/namespace
  • Database connection name
  • Id
  • Loaded relation names

This is what the queue worker uses to pull the model out of the database and do whatever needs done. The whole model is not being serialized and passed, only those four things. Or else we would be back to the problem of exposing to much to the front end. This is also why you can’t data bind in the way you were looking to do.

You can see this by going into your console and starting with livewire.components.componentsById. You will get a list of ids livewire uses, and the names you assigned of the components on the page. Tack on the id and then data: livewire.components.componentsById.{id}.data and you will see what javascript has.

So your queue, and now liviewire, has the information it needs to reach into the database and pull what it needs to work with. State is only in one spot and stays in sync, your model isn’t broadcasted to the world, and life is good.

Now why not just keep the model information there on the back end and hit the database every time instead of passing that information back and forth each time? Well it couldn’t be dynamic, and another behind the scenes aspect that was implemented with this is the database is only hit when it needs to. Without that life wouldn’t be good. If you have a text input and you’ve seen how many requests are sent back and forth as the user is typing, you don’t want your database being queried every time for no reason.

So in the end, it didn’t really change the way you code that much (although in a roundabout way it did give us computed properties). But with the other proposed solutions at the time to fix the problem, the way you code would have changed.

Hope that helps clear it up, and you can go more in depth here, here, and somewhere in here.

7 Likes

What an excellent answer xxdalexx! Very much appreciated, I have learnt a lot.

@xxdalexx really helpfull description.

… with this is the database is only hit when it needs to. Without that life wouldn’t be good. If you have a text input and you’ve seen how many requests are sent back and forth as the user is typing, you don’t want your database being queried every time for no reason.

I am not sure I follow this and could not find explanation in the docs: If we have a Livewire component with a public property set to an Eloquent model:

<?php

namespace App\Http\Livewire;

use App\User;
use Livewire\Component;

class Example extends Component
{
    public $user; // \App\User

   ...
}

then we have access to this in the blade files like $user->email. Now each time the Livewire component renders (which as you said can be many times working with input field) then our Livewire components public properties are serialized and unserialized (called hydrate and dehydrate in Livewire code) as can be seen here. Won’t that cause the $user to be refetched from the DB on each request - and if not, how do we ensure it has not been altered in the meantime?

Thanks.

I’m 95% you are correct. I was actually thinking about this a month ago or so because I didn’t think it was ever implimented, but never ended up taking the time to look into it again. When I wrote this reply, that piece was based off of

and

from this thread: Help Me Solve The Hardest Problem In Livewire (And Maybe The Entire World)

I took those comments as fact while assuming it was in the update we were discussing here, instead of actually reading through the code (like you did) and speaking to something I knew was fact. Something I don’t normally do, and this is exactly why. That’s what I get I guess.

Given that this post is still being referenced, let me pull my foot out of my mouth and edit that part out. Especially since it’s been bumped up to the top again. My apologies for the mis-information.

Edit:
I guess I can’t edit that reply. @iAmKevinMcKee are you able to make it editable, or convert it to a wiki post?

1 Like

Thanks for the quick feedback @xxdalexx and I completely understand that this was back and forth at that time - you original description is still much appreciated :wink:

In conclusion, it looks like eloquent models are in fact being fetched from DB on each livewire request (check the code here) which is backed by the final comment from Caleb:

I do however think it’s still completely possible to implement using __get(), but I personally don’t have the time to flesh it out. I don’t know how much of a hiccup collections would cause without diving into it. I’m also not sure it would be worth the time either, because as you said, as soon as you reference it in blade __get() will be hit every request anyway.