'forget' a collection's item by key within each - laravel

I was wondering if you could remove or forget an item by key when using each() method in Laravel.
I have tried this:
$Items = Items::all();
$Items->each(function($Item, $Key){
if($Item->removable){
$this->forget($Key);
}
});
And I have also tried this:
$Items = Items:all();
$Items->each(function($Item, $Key) use (&$Items){
if($Item->removable){
$Items->forget($Key);
}
});
And it seems to just be removing all items instead of the ones that would have my true conditional values (in this e.g. $Item->removable).
Can anyone shine some light on this?

Collections are usually immutable, which means that you can't actually change the underlying data in an object. Operations on a collection often return a new collection containing new items based on the original collection.
You can achieve what you're after using filter. By passing in a closure, which returns true for items that you want to keep and false for items you want to forget.
$Items = $Items->filter(function ($Item, $Key) {
return ! $Item->removable;
});
Or you can use reject which does the same as filter but in reverse, returning true forgets the item from the collection and returning false keeps the item in the collection.
$Items = $Items->reject(function ($Item, $Key) {
return $Item->removable;
});
These two methods will create a new collection, (in the above examples overwriting $Items), containing only values that are not considered to be removable.
You could just use the forget (The forget method is an exception to the immutable rule in that it does modify the underlying data) method from outside the collection to remove an item by its key but I don't think that's what you're after.
$Items->forget('your_key');

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.

Eloquent how to pass parameters to relationship

The code I'm trying to fix looks like this. I have an Hotel class which is used in a query to get all hotels in an area but it doesn't discard those which are not available. There's a method inside which should be an accessor but it's not written the way I expected it to be:
public function isAvailableInRanges($start_date,$end_date){
$days = max(1,floor((strtotime($end_date) - strtotime($start_date)) / DAY_IN_SECONDS));
if($this->default_state)
{
$notAvailableDates = $this->hotelDateClass::query()->where([
['start_date','>=',$start_date],
['end_date','<=',$end_date],
['active','0']
])->count('id');
if($notAvailableDates) return false;
}else{
$availableDates = $this->hotelDateClass::query()->where([
['start_date','>=',$start_date],
['end_date','<=',$end_date],
['active','=',1]
])->count('id');
if($availableDates <= $days) return false;
}
// Check Order
$bookingInRanges = $this->bookingClass::getAcceptedBookingQuery($this->id,$this->type)->where([
['end_date','>=',$start_date],
['start_date','<=',$end_date],
])->count('id');
if($bookingInRanges){
return false;
}
return true;
}
I wanted to filter out hotels using this query. So this is the query from the controller:
$list = $model_hotel->with(['location','hasWishList','translations','termsByAttributeInListingPage'])->get();
Is it possible to pass the range of days to the function?
By the way the first thing I tried was to use the collection after the query and pass a filter function through the collection and after that paginate manually but although it does filter, but apparently it loses
the "Eloquent" result set collection properties and it ends up as a regular collection, thus it doesn't work for me that way.
Maybe the best approach for that is to create a query scope (source) and put all your logic inside of this function.
after that you can call this scope and pass the dates. Example you will create a query scope and paste your code inside of it.
public function scopeisAvailableInRanges($query, $start_date, $end_date) {
}
then you will invoke this query scope in your controller like this.
$list = $model_hotel::isavailableinranges($start_date, $end_date)->with(['location','hasWishList','translations','termsByAttributeInListingPage'])->get();
keep in mind that inside of your query scope you will return a collection. A collection of all your available hotels.

Laravel How to pass parameter to Accessor method in model via where condition while query building?

I have a Accessor method in Collection Model getSizesAttribute, which returns array of available sizes eg: ['S','L'], Now I need to get Models with have size 'S'. like:
$collections = $collections->where('sizes','S');
But sizes is array, could I manipulate this anyhow so that I could check returns only if sizes have specific size.
I tried making another method getIsSizeAttribute, like:
public function getIsSizeAttribute($size){
return in_array($size,$this->sizes);
}
Now How could I user this in Where condition like
$collections = $collections->where('is_size','S');
Mutators and Accessors only run skin-deep, after the query's already been executed. You could use Collection::filter() as Bangnokia suggests, but that wouldn't give you any performance benefit of actually applying the condition to the initial request.
I think what you're looking for here is a Query Scope. Add something like this to your Model class:
public function scopeSize(\Illuminate\Database\Eloquent\Builder $query, $size)
{
return $query->whereIn('sizes', $this->sizes[$size]);
}
And access it like this:
$collection = $model->size('S')->get();
You should use filter on collection
$collections = $collections->filter(function($item, $index) {
return in_array('S', $item->sizes);
});

Can't modifiy eloquent query result

So I got the following code in my controller's show function which just returns a page with the tags:
$page = Post::with('tags')->findOrFail($id);
$page->tags->lists('name');
return response($page);
When I try to to execute this, it won't change the tags key, which is an array with the tags from the eloquent belongsToMany relationship.
Why isn't this working? To me it seems pretty handy to just change a value like this.
When I change it to $page->test = $page->tags->lists('name') it will add the test key as usual.
How would I modify a eloquent value in a easy way?
What works pretty well for such cases is overriding toArray in your Model:
public function toArray(){
$array = parent::toArray();
$array['tags'] = $this->tags->lists('name');
return $array;
}
After the $page = Post::with('tags')->findOrFail($id); line is executed, $page->tags is going to be an Illuminate\Database\Eloquent\Collection object containing all the related Tags for the Post. From your provided code and question, it sounds like you want to then change $page->tags to be an array containing just the related tag names.
The statement $page->tags->lists('name') is only going to return an array of all the names of the related tags; it does not modify the underlying collection. If you wanted to modify the $page->tags attribute, you would need to assign it the result of your statement:
$page->tags = $page->tags->lists('name');
However, $page->tags was an attribute that was dynamically created and assigned by the Model, and is expected to hold the contents of a relationship. Manually modifying the contents like this may have unintended consequences, but I do not know.
Edit
The Model::toArray() method merges in the relationship information over the attribute information. So, you can change the attribute, but if you echo the model, the relationship information will show up over your attribute change.
$page->tags = $page->tags->lists('name');
// this will echo the tags attribute, which is now the array of tags
echo print_r($page->tags, true);
// this will echo the model, with the tags attribute being
// overwritten with the related data
echo $page;
One option would be to unset the attribute (which also unsets the relationship) and then reassign the attribute to your desired data:
$page = Post::with('tags')->findOrFail($id);
$temp = $page->tags;
unset($page->tags); // you must unset the attribute before reassigning it
$page->tags = $temp->lists('name');
return response($page);
A little bit cleaner would be to use a different attribute name:
$page = Post::with('tags')->findOrFail($id);
$page->tagNames = $page->tags->lists('name');
unset($page->tags);
return response($page);
And another option is to do what #lukasgeiter suggested and override the Model::toArray method:
class Post extends Model {
public function toArray() {
// call the parent functionality first
$array = parent::toArray();
if (isset($this->tags)) {
$array['tags'] = $this->tags->lists('name');
}
return $array;
}
}
If you want to change the output of one of the relationships in the toArray/toJson methods, then use accessor:
// in order to not show the underlying collection:
protected $hidden = ['tags'];
// in order to append accessor to toArray output
protected $appends = ['allTags'];
// mutate the collection to be just an array of tag names
public function getAllTagsAttribute()
{
$collection = return $this->getRelation('tags');
return ($relation) ? $collection->lists('name') : [];
}
then you will get simple array instead of collection when you do $page->allTags or in the toArray/toJson output, while not showing the real collection.
It is allTags not `tags, since the latter should remain eloquent dynamic property, so you can work with it as usual before outputting anything.
not sure if this helps. To be honest, I do not get your point. But I guess there is something wrong with this line:
$page->tags->lists('name');
If $page->tags is a belongsToMany relationship and you want to add more query conditions after this relationship, you should query like this:
$page->tags()->lists('name');

laravel access model properties

I am looking for solution how to access eloquent model items by 'alias' field.
There is no problem accessing items by 'id'. But building a custom query I find myself unable to access item properties.
This piece of code works perfect
$cat = Category::find(1);
return $cat->title;
But if I am querying items with any other argument - properties are inaccessible
This code
$cat = Category::where('alias','=','vodosnab')->get();
return $cat->title;
throws an exception
Undefined property: Illuminate\Database\Eloquent\Collection::$title
Could you please help.
You already got the answer but here are some insights, when you use get() or all(), it returns a collection of model objects, which is an instance of Illuminate\Database\Eloquent\Collection, so here you'll get a Collection object
$cat = Category::where('alias','=','vodosnab')->get();
Now, you can use, $cat->first() to get the first item (Category Model) from the collection and you may also use $cat->last() to get the last item or $cat->get(1) to get the second item from the collection. These methods are available in the Collection object.
Using the first() method like Category::where('alias','=','vodosnab')->first(); will return you only a single (the first mathing item) model which is an instance of your Category model. So, use all() or get() to get a collection of model objects and you can loop through the collection like:
foreach(Category::all() as $cat) { // or Category::get()
$cat->propertyName;
}
Or you may use:
$categories = Category::where('alias','=','vodosnab')->get();
foreach($categories as $category) {
$category->propertyName;
}
Also, you may use:
$categories = Category::where('alias','=','vodosnab')->get();
$firstModel = $categories->first();
$lastModel = $categories->last();
$thirdModel = $categories->get(2); // 0 is first
If you need to get only one then you may directly use:
$category = Category::where('alias','=','vodosnab')->first();
$category->fieldname;
Remember that, if you use get() you'll get a collection of Model objects even if there is only one record available in the database. So, in your example here:
$cat = Category::where('alias','=','vodosnab')->get();
return $cat->title;
You are trying to get a property from the Collection object and if you want you may use:
$cat = Category::where('alias','=','vodosnab')->get();
return $cat->first()->title; // first item/Category model's title
return $cat->last()->title; // last item/Category model's title
return $cat->get(0)->title; // first item/Category model's title
You may read this article written on Laravel's Collection object.
get() returns a Collection of items. You probably need first() that returns a single item.

Resources