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',
],
];
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')
));
});
//
}
}