Romain's avatar
Level 30

Easy way to remove Model from Collection?

Hello,

Is there a way of removing a Model from a Collection (either Eloquent or the other one), by simply passing the model to a function? Basically I want to be able to do this:

$users = User::all();
$userToRemove = User::first();
$users->removeByModel($user); // Now $users doesn't have the first user anymore.

I've found the ways to remove a model by using the Id, using filter or map, but I was thinking that a simple method like this might already be available.

Thanks

0 likes
12 replies
LaryAI's avatar
Level 58

Yes, there is a way to remove a model from a collection by passing the model itself to a function. You can achieve this by creating a custom method in your collection class.

Here's an example of how you can implement this:

use Illuminate\Database\Eloquent\Collection;

class CustomCollection extends Collection
{
    public function removeByModel($model)
    {
        $this->items = $this->reject(function ($item) use ($model) {
            return $item->getKey() === $model->getKey();
        })->values()->all();
    }
}

In this example, we extend the Illuminate\Database\Eloquent\Collection class and add a new method called removeByModel. Inside this method, we use the reject method to filter out the model from the collection based on its primary key. Finally, we use the values method to reset the keys of the collection.

To use this custom collection, you need to replace the default collection class with your custom class in your model. For example:

use App\CustomCollection;

class User extends Model
{
    public function newCollection(array $models = [])
    {
        return new CustomCollection($models);
    }
}

Now, you can use the removeByModel method on your collection of models:

$users = User::all();
$userToRemove = User::first();
$users->removeByModel($userToRemove);

After executing this code, the $users collection will no longer contain the first user.

Note: This solution assumes you are using Laravel's Eloquent ORM.

Snapey's avatar

could you use ->except() ?

But easier not to fetch it in the first place

$users = User::where('id', '!=', $user->id)->get();

where $user is the one you don't want

Note: assuming the user you want to remove is the ->first() in a collection is dangerous and error prone

MohamedTammam's avatar

In your code example, it's better to not getting this user from the database first.

$users = User::skip(1)->get(); // Get all users without first one

For you question to remove a model from a collection by passing a model, you can combine filter and is

$users = User::all();
$userToRemove = User::first();
$filteredUsers = $users->filter(fn($user) => !$userToRevmoe->is($user));
// OR
$filteredUsers = $users->filter(fn($user) => $userToRevmoe->isNot($user));
aruszala's avatar

@romain I assume you don't want to delete the row in the database.

try:

    $users = User::all();
    $userToRemove = $users->first();

    $users = $users->filter(fn ($item) => $item->id !== $userToRemove->id);
    // Now $users doesn't have the first user anymore.
1 like
aruszala's avatar

@Romain I just tried it and it works. I believe it will take either the key or the object.

Romain's avatar
Level 30

@aruszala OK, That works since 3 weeks ago in version 10.15. I was still on 10.13! Thanks for the tip!

aruszala's avatar

@Romain I just updated my previous reply.

My previous reply was incorrect, you should use something like this:

    $users = User::all();
    $userToRemove = $users->first();

    $users = $users->filter(fn ($item) => $item->id !== $userToRemove->id);
    // Now $users doesn't have the first user anymore.

You actually DO need to pass a key to the ->forget() method. If a model is passed instead, it will use the model->id as the key to the collecion.

This leads to the wrong behavior:

  $users = User::all();
  // $users[0] contains a user with id of 1
  // $users[1] contains a user with id of 2
  // $users[2] contains a user with id of 3
  // $users[3] contains a user with id of 4
  // ...

  $userToRemove = $users->first();
  // $userToRemove contains a user with id of 1

  $users = $users->forget($userToRemove); // Removes $users[$userToRemove->id]
  // $users[0] contains a user with id of 1
  // $users[2] contains a user with id of 3
  // $users[3] contains a user with id of 4
  // ...
Romain's avatar
Level 30

Sorry guys, my example was not exactly real life. What I'm working on now is a Livewire Tag component. In that component I find a list of tags based on a user search. When the user clicks on one of the tag, it gets selected. When selected I want to remove it from the list without having to reload the list with a where clause. I hope it's clearer

Snapey's avatar

@Romain saying that in the first place would have saved time and effort by multiple people

1 like
Snapey's avatar

->except() works fine as I originally suggested.

note that eloquent collection except() method works differently to the Illuminate\Support\Collection method in that it removes an item by its primary key

$users = User::all();
$userToRemove = User::first();
$users->except($user->id); // Now $users doesn't have the first user anymore.

or for instance

$users->except(Auth::id()); 

Please or to participate in this conversation.