login event handling in laravel 5

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


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.

  1. We need to map the event to the event handler in the EventServiceProvider class under the app/Providers directory.

  2. 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.


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!