3

I am trying to test the boot() static::deleting method, which should fire when a model is deleted through Eloquent.

The command in tinker App\User::find(6)->delete(); returns a 'method [...]Collection::delete does not exist'.

If I try to use App\User::where('id', 6)->delete(); then the static::deleting method does not get triggered since Eloquent is not loaded. If I load Eloquent with ->first() then I get the same error that states method does not exist.

Here is the entire user model

 <?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    public function profile() {
        return $this->hasOne(Profile::class);
    }

    public function posts() {
        return $this->hasMany(Post::class);
    }

    public function tempUploads() {
        return $this->hasMany(TempUploads::class);
    }
    
    protected static function boot() {
        parent::boot();
        
        static::created(function ($user) {
            $user->profile()->create(['id' => $user->username, 'avatar' => '/storage/avatars/edit-profile.png']);
            mkdir(public_path() . "/storage/images/" . $user->username , 0755);

            // $data = [
            //  'user_id' => $user->username
            // ];
            // Mail::to($user->email)->send(new WelcomeMail($data));
        });

        static::deleting(function ($user) {
            $user->posts->delete();
            if ($user->profile->avatar != '/storage/avatars/edit-profile.png') {
                if ($user->profile->cover != NULL && $user->profile->cover != '') {
                    $oldAvatar = $_SERVER['DOCUMENT_ROOT'] . $user->profile->avatar;
                    $oldCover = $_SERVER['DOCUMENT_ROOT'] . $user->profile->cover;
                    if (is_file($oldAvatar) && is_file($oldCover)) {
                        unlink($oldAvatar);
                        unlink($oldCover);
                    } else {
                        die("Грешка при изтриване на стария файл. File does not exist in profile deleting method.");
                    }
                }
            }
            $user->profile->delete();
        });
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'username', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

I have spent hours now looking through google for possible solutions but nothing has yet.

How should I properly delete a User model while triggering the boot deleting method ?

6 Answers6

6

In your deleting listener you are trying to delete something else, which is a Collection which is causing the error.

$user->posts is a relationship to Posts which is a plural which is a hasMany relationship (most likely) so it returns a Collection always. Collections do not have a delete method. You will have to iterate through the collection and call delete on each Post

// calling `delete()` on a Collection not a Model
// will throw the error you see
$user->posts->delete();

// iterate through the Collection
foreach ($user->posts as $post) {
    $post->delete();
}

Side Note: you can not do any action in bulk with Models and queries and have the events be fired. All Model events are based on single instances of the Models. A direct query bypasses the Model.

lagbox
  • 48,571
  • 8
  • 72
  • 83
5

You can optimise lagbox's answer by using only one query to delete all of the posts. In his example he's executing a delete query for every post attached to the user.

For a single delete query either use the query builder of the relationship directly:

$user->posts()->delete();

or use the pluck method of the collection and a separate query:

Post::where('id', $user->posts->pluck('id'))->delete();
Dan
  • 5,140
  • 2
  • 15
  • 30
2

You can use higher order messages as well:

$user->posts->each->delete();
miken32
  • 42,008
  • 16
  • 111
  • 154
0gravity000
  • 139
  • 4
1

$user->posts->map->delete()

Dipesh C
  • 41
  • 6
  • 1
    New answers to old, well-answered questions require ample explanation as to how this complements the other answers. – Gert Arnold Jan 15 '22 at 21:27
  • its required only one line code. we can use the benefit of the observer we define during the delete model. it also takes benefit of the eloquent model provided during deleting a single model. – Dipesh C Jan 17 '22 at 08:13
  • So explain that in your answer please. Does it improve all other answers? – Gert Arnold Jan 17 '22 at 08:37
1
$user->posts()->delete()   will work

$user->posts->delete()   will not work

Because if you not put '()' then it becomes collection instead of query. delete() works on query

khatib
  • 366
  • 5
  • 7
0

I used this in my Controller File to delete the Database Entry:

public function destroy(Item $id) {
        $id->destroy($id->id);
        //return view('inv.delete', compact('id'));
        return redirect('/inv');
    }
Daniel Juric
  • 128
  • 1
  • 10