Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Michael Fayez's avatar

Please help I am tired for one week ..help me best devs.. maybe lose my job !

How to get Assignees and send them emails when the project Created .. please help me best develops This is the Project Model :-

<?php

namespace App\Models;

use App\Support\HasAdvancedFilter;
use App\Traits\Auditable;
use App\Traits\Tenantable;
use Carbon\Carbon;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;

class Project extends Model
{
    use HasFactory, HasAdvancedFilter, SoftDeletes, Tenantable, Auditable;

    public $table = 'projects';

    public const STATUES_RADIO = [
        'active' => 'Active',
        'hold'   => 'Hold',
        'closed' => 'Closed',
    ];

    protected $dates = [
        'created_at',
        'start_date',
        'end_date',
        'updated_at',
        'deleted_at',
    ];

    protected $fillable = [
        'name',
        'owner_id',
        'start_date',
        'end_date',
        'statues',
        'team_id',
        'assignee_email',
    ];

    public $orderable = [
        'id',
        'name',
        'owner.name',
        'created_at',
        'start_date',
        'end_date',
        'statues',
        'updated_at',
        'deleted_at',
        'team.name',
    ];

    public $filterable = [
        'id',
        'name',
        'owner.name',
        'created_at',
        'package.name',
        'start_date',
        'end_date',
        'statues',
        'assignee.email',
        'updated_at',
        'deleted_at',
        'team.name',
    ];

    protected function serializeDate(DateTimeInterface $date)
    {
        return $date->format('Y-m-d H:i:s');
    }

    public function owner()
    {
        return $this->belongsTo(User::class);
    }

    public function getCreatedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function package()
    {
        return $this->belongsToMany(Package::class);
    }

    public function getStartDateAttribute($value)
    {
        return $value ? Carbon::parse($value)->format(config('project.date_format')) : null;
    }

    public function setStartDateAttribute($value)
    {
        $this->attributes['start_date'] = $value ? Carbon::createFromFormat(config('project.date_format'), $value)->format('Y-m-d') : null;
    }

    public function getEndDateAttribute($value)
    {
        return $value ? Carbon::parse($value)->format(config('project.date_format')) : null;
    }

    public function setEndDateAttribute($value)
    {
        $this->attributes['end_date'] = $value ? Carbon::createFromFormat(config('project.date_format'), $value)->format('Y-m-d') : null;
    }

    public function getStatuesLabelAttribute($value)
    {
        return static::STATUES_RADIO[$this->statues] ?? null;
    }

    public function assignees(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }

    public function getUpdatedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function getDeletedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function team()
    {
        return $this->belongsTo(Team::class);
    }
}

This is the User Model :-

<?php

namespace App\Models;

use App\Models\UserAlert;
use App\Support\HasAdvancedFilter;
use App\Traits\HasTeam;
use Carbon\Carbon;
use DateTimeInterface;
use Hash;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable implements HasLocalePreference, MustVerifyEmail
{
    use HasFactory, HasAdvancedFilter, Notifiable, HasTeam, SoftDeletes;

    public $table = 'users';

    protected $casts = [
        'is_approved' => 'boolean',
    ];

    protected $hidden = [
        'remember_token',
        'password',
    ];

    protected $dates = [
        'email_verified_at',
        'created_at',
        'updated_at',
        'deleted_at',
    ];

    protected $fillable = [
        'name',
        'email',
        'password',
        'locale',
        'team_id',
        'is_approved',
    ];

    public $orderable = [
        'id',
        'name',
        'email',
        'email_verified_at',
        'locale',
        'team.name',
        'is_approved',
    ];

    public $filterable = [
        'id',
        'name',
        'email',
        'email_verified_at',
        'roles.title',
        'locale',
        'team.name',
    ];

    public function getIsAdminAttribute()
    {
        return $this->roles()->where('title', 'Admin')->exists();
    }

    public function scopeAdmins()
    {
        return $this->whereHas('roles', fn ($q) => $q->where('title', 'Admin'));
    }

    public function alerts()
    {
        return $this->belongsToMany(UserAlert::class)->withPivot('seen_at');
    }

    public function preferredLocale()
    {
        return $this->locale;
    }

    protected function serializeDate(DateTimeInterface $date)
    {
        return $date->format('Y-m-d H:i:s');
    }

    public function getEmailVerifiedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function setEmailVerifiedAtAttribute($value)
    {
        $this->attributes['email_verified_at'] = $value ? Carbon::createFromFormat(config('project.datetime_format'), $value)->format('Y-m-d H:i:s') : null;
    }

    public function setPasswordAttribute($input)
    {
        if ($input) {
            $this->attributes['password'] = Hash::needsRehash($input) ? Hash::make($input) : $input;
        }
    }

    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }

    public function getCreatedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function getUpdatedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function getDeletedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function team()
    {
        return $this->belongsTo(Team::class);
    }

    public function projects(): BelongsToMany
    {
        return $this->belongsToMany(Project::class);
    }

    


}

This is Project Observer :-

<?php

namespace App\Observers;

use App\Models\Project;
use App\Models\User;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use App\Notifications\DataChangeEmailNotification;
use Notification;

class ProjectObserver
{
    public function created(Project $project): void
    {
        $payload = [
            'action' => 'created',
            'model'  => sprintf('%s#%s', get_class($project), $project->id),
            'reason' => auth()->user(),
        ];

       
        $assignees = $project->assignees;


        Notification::send($assignees, new DataChangeEmailNotification($payload));
    }
}
0 likes
17 replies
LaryAI's avatar
Level 58

To get the assignees and send them emails when a project is created, you can modify the ProjectObserver class. Here's an example of how you can achieve this:

<?php

namespace App\Observers;

use App\Models\Project;
use App\Models\User;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use App\Notifications\DataChangeEmailNotification;
use Notification;

class ProjectObserver
{
    public function created(Project $project): void
    {
        $payload = [
            'action' => 'created',
            'model'  => sprintf('%s#%s', get_class($project), $project->id),
            'reason' => auth()->user(),
        ];

        // Get the assignees of the project
        $assignees = $project->assignees;

        // Send email notification to the assignees
        Notification::send($assignees, new DataChangeEmailNotification($payload));
    }
}

Make sure you have the DataChangeEmailNotification class defined and configured properly to send the email notification to the assignees.

Note: This solution assumes that you have already set up the necessary email configuration and notifications in your Laravel application.

Michael Fayez's avatar

@LaryAI not working getting me Null

Illuminate\Database\Eloquent\Collection {#2556 ▼ // app/Observers/ProjectObserver.php:24
  #items: []
  #escapeWhenCastingToString: false
}
Michael Fayez's avatar

@LaryAI that when I use dd($assignees)

public function created(Project $project): void
    {
        $payload = [
            'action' => 'created',
            'model'  => sprintf('%s#%s', get_class($project), $project->id),
            'reason' => auth()->user(),
        ];

       
        $assignees = $project->assignees;

        dd($assignees);
        Notification::send($assignees, new DataChangeEmailNotification($payload));
    }
Michael Fayez's avatar

@Jonjie would like to get assignees but I get empty array [] when I make dd($assignees) in project observer

public function created(Project $project): void
    {
        $payload = [
            'action' => 'created',
            'model'  => sprintf('%s#%s', get_class($project), $project->id),
            'reason' => auth()->user(),
        ];

       
        $assignees = $project->assignees;

        dd($assignees);
        Notification::send($assignees, new DataChangeEmailNotification($payload));
    }

this is the result :-

Illuminate\Database\Eloquent\Collection {#2556 ▼ // app/Observers/ProjectObserver.php:24
  #items: []
  #escapeWhenCastingToString: false
}

what shall I do ??

Jonjie's avatar

@michael fayez I am thinking if you really have assignees data in your database?

tangtang's avatar

from the project model, this code

 public function assignees(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }

is there foreign key to connect this table ? like user_id or project_id

isn't it must defined, so laravel eloquent can read the relation between table ?

updated :

can't see the key for relation in this structure

user table

 protected $fillable = [
        'name',
        'email',
        'password',
        'locale',
        'team_id',
        'is_approved',
    ];

and project table

 protected $fillable = [
        'name',
        'owner_id',
        'start_date',
        'end_date',
        'statues',
        'team_id',
        'assignee_email',
    ];

or you have another pivot table ?

Snapey's avatar
public function created(Project $project): void
    {
        $payload = [
            'action' => 'created',
            'model'  => sprintf('%s#%s', get_class($project), $project->id),
            'reason' => auth()->user(),
        ];

       
        $assignees = $project->assignees()->get();   // get the assignees

dd($assignees)

        Notification::send($assignees, new DataChangeEmailNotification($payload));
    }

if this returns empty collection then there are no assignees

Michael Fayez's avatar

@Snapey this is what I get

Illuminate\Database\Eloquent\Collection {#2531 ▼ // app/Observers/ProjectObserver.php:42
  #items: []
  #escapeWhenCastingToString: false
}

so, what's missing in my code great helper snappy :)

this is my project Model :-

<?php

namespace App\Models;

use App\Support\HasAdvancedFilter;
use App\Traits\Auditable;
use App\Traits\Tenantable;
use Carbon\Carbon;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;

class Project extends Model
{
    use HasFactory, HasAdvancedFilter, SoftDeletes, Tenantable, Auditable;

    public $table = 'projects';

    public const STATUES_RADIO = [
        'active' => 'Active',
        'hold'   => 'Hold',
        'closed' => 'Closed',
    ];

    protected $dates = [
        'created_at',
        'start_date',
        'end_date',
        'updated_at',
        'deleted_at',
    ];

    protected $fillable = [
        'name',
        'owner_id',
        'start_date',
        'end_date',
        'statues',
        'team_id',
        'assignee_email',
    ];

    public $orderable = [
        'id',
        'name',
        'owner.name',
        'created_at',
        'start_date',
        'end_date',
        'statues',
        'updated_at',
        'deleted_at',
        'team.name',
    ];

    public $filterable = [
        'id',
        'name',
        'owner.name',
        'created_at',
        'package.name',
        'start_date',
        'end_date',
        'statues',
        'assignee.email',
        'updated_at',
        'deleted_at',
        'team.name',
    ];

    protected function serializeDate(DateTimeInterface $date)
    {
        return $date->format('Y-m-d H:i:s');
    }

    public function owner()
    {
        return $this->belongsTo(User::class);
    }

    public function getCreatedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function package()
    {
        return $this->belongsToMany(Package::class);
    }

    public function getStartDateAttribute($value)
    {
        return $value ? Carbon::parse($value)->format(config('project.date_format')) : null;
    }

    public function setStartDateAttribute($value)
    {
        $this->attributes['start_date'] = $value ? Carbon::createFromFormat(config('project.date_format'), $value)->format('Y-m-d') : null;
    }

    public function getEndDateAttribute($value)
    {
        return $value ? Carbon::parse($value)->format(config('project.date_format')) : null;
    }

    public function setEndDateAttribute($value)
    {
        $this->attributes['end_date'] = $value ? Carbon::createFromFormat(config('project.date_format'), $value)->format('Y-m-d') : null;
    }

    public function getStatuesLabelAttribute($value)
    {
        return static::STATUES_RADIO[$this->statues] ?? null;
    }

    public function assignees(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }

    public function getUpdatedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function getDeletedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function team()
    {
        return $this->belongsTo(Team::class);
    }
}

this is my User Model :-

<?php

namespace App\Models;

use App\Models\UserAlert;
use App\Support\HasAdvancedFilter;
use App\Traits\HasTeam;
use Carbon\Carbon;
use DateTimeInterface;
use Hash;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable implements HasLocalePreference, MustVerifyEmail
{
    use HasFactory, HasAdvancedFilter, Notifiable, HasTeam, SoftDeletes;

    public $table = 'users';

    protected $casts = [
        'is_approved' => 'boolean',
    ];

    protected $hidden = [
        'remember_token',
        'password',
    ];

    protected $dates = [
        'email_verified_at',
        'created_at',
        'updated_at',
        'deleted_at',
    ];

    protected $fillable = [
        'name',
        'email',
        'password',
        'locale',
        'team_id',
        'is_approved',
    ];

    public $orderable = [
        'id',
        'name',
        'email',
        'email_verified_at',
        'locale',
        'team.name',
        'is_approved',
    ];

    public $filterable = [
        'id',
        'name',
        'email',
        'email_verified_at',
        'roles.title',
        'locale',
        'team.name',
    ];

    public function getIsAdminAttribute()
    {
        return $this->roles()->where('title', 'Admin')->exists();
    }

    public function scopeAdmins()
    {
        return $this->whereHas('roles', fn ($q) => $q->where('title', 'Admin'));
    }

    public function alerts()
    {
        return $this->belongsToMany(UserAlert::class)->withPivot('seen_at');
    }

    public function preferredLocale()
    {
        return $this->locale;
    }

    protected function serializeDate(DateTimeInterface $date)
    {
        return $date->format('Y-m-d H:i:s');
    }

    public function getEmailVerifiedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function setEmailVerifiedAtAttribute($value)
    {
        $this->attributes['email_verified_at'] = $value ? Carbon::createFromFormat(config('project.datetime_format'), $value)->format('Y-m-d H:i:s') : null;
    }

    public function setPasswordAttribute($input)
    {
        if ($input) {
            $this->attributes['password'] = Hash::needsRehash($input) ? Hash::make($input) : $input;
        }
    }

    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }

    public function getCreatedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function getUpdatedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function getDeletedAtAttribute($value)
    {
        return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null;
    }

    public function team()
    {
        return $this->belongsTo(Team::class);
    }

    public function projects(): BelongsToMany
    {
        return $this->belongsToMany(Project::class);
    }

    


}

this is the pivot table

Schema::create('project_user', function (Blueprint $table) {
            $table->unsignedBigInteger('project_id');
            $table->foreign('project_id', 'project_id_fk_9107252')->references('id')->on('projects')->onDelete('cascade');
            $table->unsignedBigInteger('user_id');
            $table->foreign('user_id', 'user_id_fk_9107252')->references('id')->on('users')->onDelete('cascade');
        });

what's missing please ,, would like to get assignees and send them email

jlrdw's avatar

@Michael Fayez have you tried to use a combination of dd as needed and the network tab (response and request) to see what is happening.

Normally using these tools helps me figure things out.

Michael Fayez's avatar

@jlrdw yes and this is the result

Illuminate\Database\Eloquent\Collection {#2531 ▼ // app/Observers/ProjectObserver.php:42
  #items: []
  #escapeWhenCastingToString: false
}
jlrdw's avatar

@Michael Fayez if results aren't correct, that's where you try other things, i.e., dig into documentation and the API to figure out where your code is not correct.

Maybe even try some test.

Just suggestions.

kokoshneta's avatar

@Michael Fayez There’s not necessarily anything wrong with your code. But have you checked that there are actually any users assigned to the project you’re loading?

What is your $project->id? And what do you get if you look in your database and select * from project_user where project_id = [your $project->id]?

If you don’t get any rows, that’s obviously the problem: it’s fetching all the assigned users, but you haven’t assigned any users, so there’s nothing to fetch.

2 likes
krisi_gjika's avatar

@Michael Fayez how are you relating the project to assignees? I would assume you are creating the project and than filling the pivot table with that project id. The issue would be that on the project created event, ie. your observer, the pivot table is not yet filled and your relation would be empty.

Try to send the email after you have filled the relation, meaning not from your observer. Also I would consider sending emails from an observer not so great practice. It's too "hidden" for such a crucial feature as notifications, and you, or another developer, can trigger it without realising, since checking what observer might trigger is not something we often think about.

DhPandya's avatar

@michael fayez Didn't you think that the given relationship on the Project model is not appropriate. What model is:

public function assignees(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }

What should model have

public function assignees(): HasMany
    {
        return $this->hasMany(User::class);
    }

Because in the term we spoke Project has many assignees not project is belongs to assignees.

kokoshneta's avatar

@DhPandya Absolutely not. The structure of the models clearly shows that the relationship between projects and assignees is a many-to-many relationship, which means both Project::assignees() and User::projects() should use ->belongsToMany(). Using ->hasMany() will not work at all when there is a pivot table.

1 like
Snapey's avatar

@DhPandya project could have many assignees, and a user could be assigned to many projects - so a belongs to many would surely be appropriate.

But, the OP does not really know what they are doing and cannot answer this in this question or their previous question

1 like

Please or to participate in this conversation.