Laravel Eloquent $model->getChanges() are always empty in updated event - php

I'm trying to push all the model changes to the frontend using updated event. I don't want to send the whole model so I found the hasChanges() method. But it's always empty.
My first thought was that this event fires BEFORE the actual save but getDirty() is also empty. Then I thought in debug bar that for some reason it's retrieving the model once again (selecting from DB) right after updating it. Is this normal behaviour or is it just creating a new model object and not passing the existing one to the event?
Event:
class IcUpdated implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
private $ic;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($ic)
{
$this->ic = $ic;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return [
new Channel('dashboard_' . ConfigHelper::getSelectedOrganizationId())
];
}
public function broadcastAs()
{
return 'ic.updated';
}
public function broadcastWith()
{
return $this->ic->getChanges();
}
}
Model:
protected $dispatchesEvents = [
'updated' => \App\Events\IcUpdated::class,
];
So how would I access and send only changed fields in event?

This is caused by the SerializesModels trait. This causes the model to be serialized to its primary key and then refetched from the database when the job is executed.
This is useful for cases where there is a delay on a queued job, for instance, you queue an email to go out to $user. The user changes their email address, the queued job runs but goes out to the new email address since it refetches the user from the database.
In your case, you definitely don't want to serialize the model to its key since you need properties stored in that specific model instance.

Related

Weird bug when passing data to Laravel notification constructor

I have a variable, called $applicants and it contains data from users table and other tables from eager load.
Something like this:
Then I pass that variable to a Laravel Notification class via __construct method. The problem is if I dd the $applicant in the __construct method, the data is preserved, but if I dd it in the toMail method, it only contains data from user table.
Here is the code:
class DailyReportWasGenerated extends Notification implements ShouldQueue {
use Queueable;
private $applicants;
/**
* Create a new notification instance.
*
* #param $applicants
*/
public function __construct($applicants)
{
$this->applicants = $applicants;
dd($this->applicants->toArray());
}
public function toMail($notifiable)
{
dd($this->applicants->toArray());
}
I found the reason here: Relationship not being passed to notification?
So my solution is just convert my model collection to an array.

Have no idea why the listener is not being called in Laravel

Heres the event:
<?php
namespace App\Modules\Clinicians\Events;
use Illuminate\Queue\SerializesModels;
class CreateHealthCareProviderEvent
{
use SerializesModels;
public $data;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(array $data)
{
$this->data = $data;
}
}
The event gets called just fine. But the listener:
<?php
namespace App\Modules\Clinicians\Listeners;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Modules\Clinicians\Events\CreateHealthCareProviderEvent;
use App\Modules\Clinicians\Events\EmailClinicAdminsEvent;
use App\Modules\Users\Services\RegisterUserService;
class CreateHealthCareProviderListener
{
private $registerUserService;
/**
* Create the event listener.
*
* #return void
*/
public function __construct(RegisterUserService $registerUserService)
{
$this->registerUserService = $registerUserService;
}
/**
* Handle the event.
*
* #param object $event
* #return void
*/
public function handle(CreateHealthCareProviderEvent $event)
{
$user = $this->registerUserService->setRequestData($event->data)->registerUser(Clinic::find($event->data['clinic_id']));
$user->clinician()->create([
'user_id' => $user->id,
]);
$user->clinician->clinics()->attach($event->data['clinic_id']);
event(new EmailClinicAdminsEvent($user, $event->data['clinic_id']));
}
}
Never gets called. Ever. So how am I registering these?
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
// When a new health care provider registers:
CreateHealthCareProviderEvent::class => [
CreateHealthCareProviderListener::class,
],
// Email the Clinic admins when a healthcare provider registers:
// Called in the CreateHealthCareProviderListener handler method.
EmailClinicAdminsEvent::class => [
EmailClinicAdminsListener::class,
],
];
...
}
I have never had an issue registering events and listeners like this before. They always work. But for some reason the listener will not fire for this event. The event fires just fine. But not the listener. What am I missing?
I have tried:
php artisan optimize
composer dump-autoload
Nothing.
Its called as such:
// Create the health care provider user:
event(new CreateHealthCareProviderEvent($request->all()));
Any ideas? I checked spelling and namespaces and everything seems correct. What am I missing?
There are no errors, nothing. The tests still pass (I dont fake events) and the test coverage shows the event gets called, but the test coverage shows the listener does not get called. When I try this out in the browser, no user is created - which is done by the listener.
All my other events and their associated listeners are called just fine.
What is going on?
So I am retarded and will take the downvotes on this one but for future reference, make sure your use statements are proper. because Laravel won't tell you when registering events:
use App\Modules\Clinicians\CreateHealthCareProviderEvent;
use App\Modules\Clinicians\EmailClinicAdminsEvent;
use App\Modules\Clinicians\CreateHealthCareProviderListener;
use App\Modules\Clinicians\EmailClinicAdminsListener;
What do you think is missing here? let me tell you:
use App\Modules\Clinicians\Events\CreateHealthCareProviderEvent;
use App\Modules\Clinicians\Events\EmailClinicAdminsEvent;
use App\Modules\Clinicians\Listeners\CreateHealthCareProviderListener;
use App\Modules\Clinicians\Listeners\EmailClinicAdminsListener;
This is why the event was being called but not the listener. In the controller class where the event in question was being called, I imported the event properly, but in the event service provider I did not.

Laravel / Eloquent: how to hook into the deletion process?

When a record gets deleted from my_items_table I want to insert the record into my_items_table_archive.
I could do this on each Controller, but would prefer to hook into the Eloquent model.
Is there anything like this?
Pseudocode:
class MyItem extends Model {
protected function beforeDelete($record) {
MyItemArchive::create($record); // add record the archive
return true; // continue deletion of $record
}
}
Any idea? Thanks!
Yes, there is something similar to your pseudocode.
You can utilise Eloquent Events
A good example of this can be seen below:
protected $dispatchesEvents = [
'deleted' => UserDeleted::class,
'deleting' => UserDeleting::class
];
The class in question just needs to adhere to / Follow: Listeners
You can also use Eloquent Observers / the observer pattern to achieve a similar result.
Let me know how you get on!
First of all create a new Observer using
php artisan make:observer MyItemObserver
Then
<?php
namespace App\Observers;
class MyItemObserver
{
public function deleting(MyItem $myItem)
{
/// insert new record here
}
}
Now you in your appServiceProvider
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
MyItem::observe(MyItemObserver::class);
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}
Now your obverserver will be hooked to Model Events.
Hope this helps.
As described in the official documentation you have two choices using the events. The first one is creating an observer like this:
class MyModelObserver
{
/**
* Listen to the Model deleting event.
*
* #param User $user
* #return void
*/
public function deleting(User $user)
{
// HERE YOUR CODE TO TRANSFER THE MODEL
}
}
Than you have to register it on your AppServiceProvider
public function boot {
MyModel::observe(MyModelObserver::class)
}
Otherwise you can add these events in your model by generating the specific class:
protected $dispatchesEvents = [
'deleting' => MyModelDeletingEvent::class,
];
Anyway if you're using a version of laravel lower than 5.4 you should check the documentation for the specific implementation, since the $dispatchesEvents is not available as variable.

Laravel: if user wasRecentlyCreated top up balance

My developer left , so I need to finish our project by myself.
I'was doing Automation using C# so, have some knowledge in coding.
Question is:
How correctly top up new user's balance?
I have in SQL table - users, where all users are stored. (user_id +
name/surname + reg date)
I have in SQL table user_balance, where all users with balance are
stored (user_id + balance ammount)
So, I need to somehow gift to a new user some money.
Do I need to work with blade view, and trying something with
#if user reg date == bla bla
sql query
#else
ignore
#endif
or, better to create with controllers, models?
As #Nate points out, model events will get you what you need, howver, I'd use the Creating event rather than Created as then you can set the balance as the record is saved, saving you the update query.
You definitely do not want to do this in the blade view. Try to keep all business logic out of view files and contained within controllers/models/event listeners, etc.
You can simplify this from the other answer by adding the event handling within the model's static boot method.
public static function boot()
{
parent::boot();
static::creating(function($model)
{
$model->balance = 100;
});
}
Taken from another similar answer, you need to use events, when the user is created
Inside your User's model, you can create an event handlers like so:
/**
* The event map for the model.
*
* #var array
*/
protected $dispatchesEvents = [
'created' => \App\Events\UserCreatedEvent::class,
];
Then you can create an event like so:
UserCreatedEvent
<?php
namespace App\Events;
use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
class UserCreatedEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
/**
* Create a new event instance.
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
Then you can create a listener to create the balance:
UserCreatedListener
<?php
namespace App\Listeners;
use Illuminate\Support\Facades\Mail;
use App\Events\UserCreatedEvent;
class UserCreatedListener
{
/**
* Create the event listener.
*/
public function __construct()
{
}
/**
* Handle the event.
*
* #param UserCreatedEvent $event
*/
public function handle(UserCreatedEvent $event)
{
// update their balanace here
$event->user->update(['balance' => 1000]);
}
}
Then inside your eventserviceprovider.php add
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
'App\Events\UserCreatedEvent' => [
'App\Listeners\UserCreatedListener',
],
];

login event handling in laravel 5

i am trying to hook to the login even in my L5 app to set last login time and IP address. i can make it work with the following:
Event::listen('auth.login', function($event)
{
Auth::user()->last_login = new DateTime;
Auth::user()->last_login_ip = Request::getClientIp();
Auth::user()->save();
});
however, i am wondering what the best way to do this in L5 is with the event handler object. i tried creating an event handler and adding auth.login as an array key in the events service provider, however that didnt work. im not sure if that is possible or not with the auth.login event. if it isnt, where is the most appropriate place to put the above code. for testing, i put it in my routes.php file, but i know that isnt where it should be.
In laravel 5.2; auth.login won't work... the following will have to be used:
protected $listen = [
'Illuminate\Auth\Events\Attempting' => [
'App\Listeners\LogAuthenticationAttempt',
],
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LogSuccessfulLogin',
],
'Illuminate\Auth\Events\Logout' => [
'App\Listeners\LogSuccessfulLogout',
],
'Illuminate\Auth\Events\Lockout' => [
'App\Listeners\LogLockout',
],
];
As stated in the documentation here
EDIT: this only works in 5.0.* and 5.1.*.
For the 5.2.* solution see JuLiAnc response below.
after working with both proposed answers, and some more research i finally figured out how to do this the way i was trying at first.
i ran the following artisan command
$ php artisan handler:event AuthLoginEventHandler
Then i altered the generated class removing the import of the Event class and and imported the user model. I also passed User $user and $remember to the handle method since when the auth.login event is fired, thats what is passed.
<?php namespace App\Handlers\Events;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldBeQueued;
use App\User;
class AuthLoginEventHandler {
/**
* Create the event handler.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param User $user
* #param $remember
* #return void
*/
public function handle(User $user, $remember)
{
dd("login fired and handled by class with User instance and remember variable");
}
}
now i opened EventServiceProvided.php and modified the $listen array as follows:
protected $listen = [
'auth.login' => [
'App\Handlers\Events\AuthLoginEventHandler',
],
];
i realized if this doesn't work at first, you may need to
$ php artisan clear-compiled
There we go! we can now respond to the user logging in via the auth.login event using an event handler class!
Be careful about asking what the best way to do X is, because Laravel, in particular, provides many ways of accomplishing the same task -- some are better than others in certain situations.
Taking a look at the Laravel documentation, personally I would go with the "Basic Usage" as it seems to match the use case you have stated.
If we run the following Artisan command we can generate a template for the UserLoggedIn event.
$ php artisan make:event UserLoggedIn
(note the past tense, because events happen, and then the subscribers are notified of the event having taken place)
(note 2: the app string in namespaces is what Laravel uses out of the box, it is likely different for you if you have executed the php artisan app:name command)
The following class is generated for us:
<?php namespace app\Events;
use app\Events\Event;
use Illuminate\Queue\SerializesModels;
class UserLoggedIn extends Event {
use SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct()
{
//
}
}
If we add a userId parameter to the constructor, then the event doesn't need to know about the Auth Facade/Guard Contract. This means our UserLoggedIn event code is not tightly coupled to Eloquent or which ever authentication framework you decide to utilize in your app. Anyways, let's add that userId parameter.
<?php namespace app\Events;
use app\Events\Event;
use app\User;
use Illuminate\Queue\SerializesModels;
class UserLoggedIn extends Event {
use SerializesModels;
public $userId;
/**
* Create a new event instance.
*
* #param int userId the primary key of the user who was just authenticated.
*
* #return void
*/
public function __construct($userId)
{
$this->userId = $userId;
}
}
Now you're probably wondering, well that's great and all, but how to we act on this event? Great question! We need to create an event handler to handle when this event is fired. Let's do that now using Artisan:
$ php artisan handler:event UpdateUserMetaData --event=UserLoggedIn
We name our new event handler UpdateUserMetaData and tell Artisan that the event we want to handle is the UserLoggedIn event.
Now we have some code that looks like this inside of app/Handlers/Events/UpdateUserMetaData.php:
<?php namespace app\Handlers\Events;
use app\Events\UserLoggedIn;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldBeQueued;
class UpdateUserMetaData {
/**
* Create the event handler.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param UserLoggedIn $event
* #return void
*/
public function handle(UserLoggedIn $event)
{
//
}
}
We can update the handle method to be able to handle this event like you specified above quite easily:
<?php namespace app\Handlers\Events;
use app\Events\UserLoggedIn;
use Illuminate\Http\Request;
class UpdateUserMetaData {
protected $request;
/**
* Create the event handler.
*
* #param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* Handle the event.
*
* #param UserLoggedIn $event
*/
public function handle(UserLoggedIn $event)
{
$user = User::find($event->userId); // find the user associated with this event
$user->last_login = new DateTime;
$user->last_login_ip = $this->request->getClientIp();
$user->save();
}
}
As a side note, if you're not familiar with Carbon, you might want to look into using it so you can take advantage of its fantastic API like you can with Eloquent's created_at and updated_at timestamp fields on most models. Here's a link for how to tell Eloquent which fields should be used with Carbon: http://laravel.com/docs/master/eloquent#date-mutators.
There are two final steps we have to perform before this code will work in your Laravel app.
We need to map the event to the event handler in the EventServiceProvider class under the app/Providers directory.
We need to fire the event after login.
To complete the first step, we just need to add our event classes to the $listeners property in app/Providers/EventServiceProvder.php like so:
UserLoggedIn::class => [
UpdateUserMetaData::class
]
The above will work provided you import the classes inside the EventServiceProvider class, and you are using PHP 5.5. If you're using a lower PHP version, you'll need to provide the full path to each class as a string like this: 'app/Events/UserLoggedIn' and 'app/Handlers/Events/UpdateUserMetaData'.
The $listeners array maps events to their respective handlers.
Okay, now for the final step! In your code base, find the place where the user is authenticated and add the following:
event(new \app\Events\UserLoggedIn(Auth::user()->id));
And we're done! I tested this code as I wrote this answer, feel free to ask follow up questions if you have any.
For 5.2 something like this
in Listeners:
use Carbon\Carbon;
use Illuminate\Auth\Events\Login;
class UpdateLastLoginWithIp
{
public function handle(Login $event)
{
$event->user->last_login_at = Carbon::now();
$event->user->last_login_ip = Request::getClientIp()
$event->user->save();
}
}
In EventServiceProvider.php :
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'City\Listeners\UpdateLastLoginWithIp',
],
];
Usually you can achieve by doing like this step by step for User Login Logs
first, you should have Auth Scaffolding
use this as event,
'Illuminate\Auth\Events\Login' for Login Event
'Illuminate\Auth\Events\Logout' for Logout Event
located the login and logout event at :
vendor\laravel\framework\src\Illuminate\Auth\Events
EventServiceProvider.php
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LoginLogs',
],
'Illuminate\Auth\Events\Logout' => [
'App\Listeners\LogoutLogs',
],
];
public function boot()
{
parent::boot();
}
then after you get done for EventServiceProvider, do this next step
type this artisan command php artisan event:generate
look for folder Listener inside App folder, check if contains php files both LoginLogs and LogoutLogs
create your migration and model
command: php artisan make:migration create_UserLoginHistory
Migration File
public function up()
{
Schema::create('tbl_user_login_history', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('user_id');
$table->datetime('login_at')->nullable();
$table->datetime('logout_at')->nullable();
$table->string('login_ip')->nullable();
$table->string('role');
$table->string('session_id');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('tbl_user_login_history');
}
then your Model : UserLoginHistory
public $timestamps = false;
protected $table = 'tbl_user_login_history';
protected $fillable = ['user_id','login_at','logout_at','login_ip','role','session_id'];
public function setLogOutLog(){
$this->where('session_id',request()->session()->getId())->update([
'logout_at' =>Carbon::now(),
]);
}
public function setLogInLog(){
$this->insert(
['user_id' => Auth::user()->id,'login_at' =>Carbon::now(),
'login_ip'=>request()->getClientIp(),'role' =>Auth::user()->role,
'session_id'=>request()->session()->getId()
]);
}
4.after the migration and model creation procedure, let's assume that you have already in roles in users table
the listener part
Listener : LoginLogs Class
use App\UserLoginHistory;
private $UserLoginHistory;
public function __construct(UserLoginHistory $UserLoginHistory)
{
// the initialization of private $UserLoginHistory;
$this->UserLoginHistory = $UserLoginHistory;
}
public function handle(Login $event)
{
// from model UserLoginHistory
$this->UserLoginHistory->setLogInLog();
}
Listener : LogoutLogs Class
private $UserLogoutHistory;
public function __construct(UserLoginHistory $UserLoginHistory)
{
// the initialization of private $UserLogoutHistory;
$this->UserLogoutHistory = $UserLoginHistory;
}
public function handle(Logout $event)
{
// from model UserLoginHistory
$this->UserLogoutHistory->setLogOutLog();
}
after you do this all steps , try to login with Auth
here is my approach:
I have done for making an event handler when user logged in using:
Laravel 5.8
1) Run the following artian command
php artisan make:listener Auth/UserLoggedIn --event='Illuminate\Auth\Events\Login'*
It will make a Listener: UserLoggedIn in folder app\Listeners\Auth\
2) Then you need to add this listener into your EventServiceProvider:**
...
protected $listen = [
...
'Illuminate\Auth\Events\Login' => [
'App\Listeners\Auth\UserLoggedIn',
],
];
Finaly you can do log when user logged in in handle function located at UserLoggedIn Listener:
public function handle(Login $event)
{
//you have access to user object by using : $event->user
}
you can use all other Auth events, here is the possible events:
'Illuminate\Auth\Events\Registered',
'Illuminate\Auth\Events\Attempting',
'Illuminate\Auth\Events\Authenticated',
'Illuminate\Auth\Events\Login',
'Illuminate\Auth\Events\Failed',
'Illuminate\Auth\Events\Logout',
'Illuminate\Auth\Events\Lockout',
**You can use all these events in your EventServiceProvider:
https://laravel.com/docs/5.8/authentication#events
Open up EventServiceProvider.php and in boot method you can listen for 'auth.login' event via callback.
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->listen('auth.login', function()
{
dd('logged in event');
});
}
You may want to create listener so you move callback function somewhere else. Do that following this http://laravel.com/docs/4.2/events#using-classes-as-listeners
just did it this way
<?php
namespace App\Providers;
use App\User;
use Auth;
use DB;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
];
/**
* Register any other events for your application.
*
* #param \Illuminate\Contracts\Events\Dispatcher $events
* #return void
*/
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->listen('auth.login', function()
{
DB::table('users')
-> where('id', Auth::id())
-> update(array(
'last_login' => date('Y-m-d H:i:s')
));
});
//
}
}

Categories