Livewire: persist custom objects in Livewire component after wire:click? - laravel

Can I keep using a collection of custom objects throughout a Livewire lifecycle?
I create a collection, show them in a list, and take action when the user selects one.
At the moment they are still an object in the blade #foreach (i.e. {{ $item->name }}, but end up as array after the wire:click (i.e. $item['name']), which breaks running the same #foreach again after completing the wire:click method.
But more importantly, each custom object contains a collection of models and they are converted to plain array as well.
It seems this is currently expected behavior as Livewire does not know how to rehydrate them (unlike Eloquent models).
I was hoping that I could store the objects in protected property but these do not persist, just like the documentation says.
Is there a way to achieve a similar flow where I display a list (using data from custom objects) and take action on the selected custom object?

protected properties truly are only useful for variables that are consistent, such as rules, or variables that are set each request that cannot be public.
As for the collection issue, it seems like the answer is already in the github issue thread you linked, by simply re-initiating the arrays as object. It (for now) is expected behaviour as it cannot rehydrate. You can do a map on the collection:
$this->customCollection = $this->customCollection->map(function($item) {
return is_array($item) ? (object) $item : $item;
});
or a foreach like so:
foreach ($this->customCollection as $index => $item) {
if (is_array($item)) {
$this->customCollection[$index] = (object) $item;
}
}
For each nest of collections, you'll have to do the same looping if you specifically want custom objects. It'll probably lose out on performance and you're probably better off using Eloquent collections/models or pure arrays.

Related

Most efficient way to bind livewire input with looped/nested properties

I have a livewire component that has about 25 properties.
public $prop1=[], $prop2=[], $prop3=[] etc. In my view, I'm looping over a collection and displaying a form that would normally have all these properties bound to it with wire:model. But the idea of the loop is throwing me off. I have something like wire:model="prop1.{{$key}}" but because I'm not declaring the property value in the render/mount functions, binding to the property array obviously isn't working. My question is what's the most efficient way to treat this scenario? Should I have a loop in the render or mount function that loops over the collection like so:
public function render()
{
$this->collection_items = Items::all();
foreach($this->collection_items as $key=>$item)
{
$this->prop1[$key] = $item->prop1;
$this->prop2[$key] = $item->prop2;
$this->prop3[$key] = $item->prop3;
..........
}
return view('livewire.my-view');
}
and then in my view I would be able to do wire:model="prop1.{{$key}}"? Or is there some other fancy way to do this without the loop in the render method that could accomplish this in a cleaner way?
Instead of defining each and every property of your model separately, you can wrap them in an array:
public $items = [];
Then, you can use your key as subkey, and use fill to make quick work of your properties:
foreach($this->collection_items as $key => $item) {
$this->fill(["items.{$key}" => $item->getAttributes()]);
}
Then, all your $attributeKey => $attributeValue will be set under $this->items[$key]. In your view, you can then set your wire:model with dot notation in whatever way you wish to work these properties:
wire:model="items.{{$key}}.prop1"
You can also fill it as collection, simply by calling $this->fill([$items => Items::all()]), but then you can't wire:model to an attribute.

Laravel: retrieving one item from a relationship

I am using Laravel Eloquent to retrieve data from the database.
I want to get the related data as an object not an array ( whats inside the texts table), so it is easier to work on the data on the blade file. This is my code I tried using first() but it doesn't work
Icon::with(["texts" => function($query) use ($language){
$query->where("language_id",$language->id)->first();
}, "texts.language"])->get();
How to Acheive it?
You could create an accessor function that provides a shorthand attribute to the required value.
In your Icon class:
public function getTextAttribute() {
// You'd only have to provide the $language somehow.
return $this->texts()->where('language_id', $language->id)->first();
}
Elsewhere, like in Blade, when you're using an Icon you can then use:
{{$icon->text}}

Laravel overwrite toArray with custom parameter

In my controller I have:
$Locations = Locations::where(something);
$Locations->get()->toArray(true);
And inside the model:
function toArray($include_all = false) {
var_dump($include_all);
}
The include all variable is false, although the function gets called.
Is there a reason why it's doing that ?
I want to call a custom toArray because I have more oneToMany relations with different structures that I want to change (some of them are serialized for example)
Thank you
You can use Illuminate\Support\Collection methods such as map() and filter() to modify the collection and at the end of that call toArray() method.
It would be fairly easy to overwrite, however I think there is some confusion that should be cleared up first.
First, the toArray() method you are calling in this case is on the Collection which is the object which is returned when you use get() on your model.
With that said, you can add the following to your Location model to return a custom collection...
public function newCollection(array $models = [])
{
return new CustomCollection($models);
}
Then you write the new CustomCollection class with appropriate namespaces just to make sure it gets auto loaded fine, have it extend \Illuminate\Database\Eloquent\Collection and then you can proceed to override the toArray method.
However, it feels like you randomly selected this toArray() as a proper candidate to perform your logic just because you are already using it. You should think about creating a new function which calls $this->toArray() to grab the results and modify them as you need and return that.
If you need this same functionality on other models, just keep adding that newCollection method where needed.
This is also in the docs as well, might be worth checking out...
https://laravel.com/docs/5.2/eloquent-collections#custom-collections

Laravel 5 - generic document management

I have a system where you can create different types of unique documents. For instance, one document is called Project Identified and this expects certain inputs. Originally, I had a database table for each unique document type, but this was getting messy fast. So, I created a database structure that was more generic, and came up with the following
So, I create a project. Within the projects show page, I can select the type of document I want to create e.g.
<li>{!! link_to_route('projects.documents.create', 'Project Identified', array($project->id, 'documentType' => 'projectIdentified')) !!}</li>
Now if I select to create a Project Identified document, it uses the generic Document Controller to handle things. Because the link to route has a documentType param, I can grab the value of this from the url. As such, in my Document Controllers create function, I am doing the following to display the correct view for the document
public function create(Project $project)
{
$documentType = $_GET["documentType"];
if($documentType == "projectIdentified") {
return View::make('projectIdentifiedDoc.create', compact('project'));
}
}
This view has a form which is binded
{!! Form::model(new App\Document, [
'class'=>'form-horizontal',
'route' => ['projects.documents.store', $project->id]
]) !!}
However, within the document controllers store function, I once again need to get the documentType. How can I pass this within the forms model? Also, is this the correct way to do this or is there a more efficient way?
Thanks
Have you read the documentation on relationships?
https://laravel.com/docs/5.2/eloquent-relationships
You need to define the relationship within your model.
So if a document only has one documentType, within your document model, you would define
public function documentType()
{
return $this->hasOne('App\documentType');
}
The different types of relationship, how to define them, and then how to access that data, is all very well documented.

How can I "defang" a Doctrine2 Entity?

I'd like to create an "API-like" layer in my code that effectively cordons-off database access to higher level code. For example, I might have the following function:
class MyApi {
private $my_user_id;
function getContacts() {
$contacts = $em->getRepository('Contacts')->findByOwner($this->my_user_id);
$em->clear();
return $contacts;
}
function getGroups() {
$groups = $em->getRepository('Groups')->findByOwner($this->my_user_id);
//hydrate each group's contacts list
foreach ($groups as $group) {
$group->getContacts();
}
$em->clear();
return $groups;
}
}
I'm using $em->clear() to detach the Entities from the EntityManger before returning them, so my Views can't accidentally modify managed entities. However, I run into problems when I want to compare entities returned by two sequential API functions. Ideally, I'd like a view/controller to contain:
$my_contacts = $myapi->getContacts();
$my_groups = $myapi->getGroups();
foreach($my_groups as $group) {
foreach ($my_contacts as $contact) {
if ($group->getContacts()->contains($contact)) {
echo "{$group->getName()} contains {$contact->getName()}!<br/>";
} else {
echo "{$group->getName()} does not contain {$contact->getName()}!<br/>";
}
}
}
However, since I detached all of the Contacts from the EntityManager at the end of the getContacts API call, the objects returned by $group->getContacts() are different PHP objects than those returned by $api->getContacts(), so the contains() function doesn't work properly.
Do I have any options for "defanging" my entities, making them effectively read-only, without giving up the benefits that the EntityManager provides? (Such as all managed entities representing the same database entry being the same object, being able to further hydrate associated objects after they've been passed back from the API, etc.)
Why would you worry that your views are going to make changes that will be committed back to the database? If your views don't know about the EM (and they shouldn't), any changes they make to the entities will disappear at the end of the request.
The only other option I can think of is to hydrate your results as arrays when they're destined to be fed to the view script. But that gives up a lot of handy functionality.
Maybe this is a little late, but it can be useful for anyone who still needs answer on this issue...
I think there is missing a Domain Driven Design principle here: Command Query Separation.
On every object you can only have two kind of methods:
doSomething() {
//this kind of method can change the internal state
}
getSomething() {
//this kind of method NEVER changes internal state
}
Keeping proper MVC in mind, views should only need get-methods and they can never change a thing.
Detaching you entities is not necessary and no worries are needed, if keeping just CQS and MVC in mind.

Resources