Plugin Development: Model Events

Previous Section: Form Requests


SupportPal was built with Eloquent ORM, an ActiveRecord implementation with the idea that each record, be it a ticket, user or even an activity log message is a model. Each model has a lifecycle, firing different events when the model is created, updated or deleted. Model events allow you to hook on to these events and use or update the model data as needed.

Contents

Why Use Model Events?

Model events let you listen for changes to a given model and lets you perform an action or alter the model data before the event completes. Example cases where this is useful:

Ticket Events

We've created a set of specific ticket events that can be listened to after a ticket has been created or a certain change has happened to an existing ticket. These are only available after the create/update has taken place and cannot be used to modify data before it is saved to the database. We recommend to use these over normal model events (described further below) in most cases as creation and updates to tickets are often complex and can involve multiple models or multiple events.

Event Event Data Description
\App\Modules\Ticket\Events\TicketCreated $ticket - The ticket model Fired after a new ticket is created, including the first message on the ticket.
\App\Modules\Ticket\Events\UserReplyCreated $ticket - The ticket model
$message - The message model
Fired after a new reply is posted by a user. Only fired on existing tickets, use the TicketCreated event for new tickets.
\App\Modules\Ticket\Events\OperatorReplyCreated $ticket - The ticket model
$message - The message model
Fired after a new reply is posted by an operator. Only fired on existing tickets, use the TicketCreated event for new tickets.
\App\Modules\Ticket\Events\OperatorNoteCreated $ticket - The ticket model
$message - The message model
Fired after a new note is posted by an operator.
\App\Modules\Ticket\Events\OperatorForwardCreated $ticket - The ticket model
$message - The message model
Fired after a ticket is forwarded by an operator.
\App\Modules\Ticket\Events\DepartmentUpdated $ticket - The ticket model
$before - The old department ID
$after - The new department ID
Fired after the department has been updated on the ticket.
\App\Modules\Ticket\Events\StatusUpdated $ticket - The ticket model
$before - The old status ID
$after - The new status ID
Fired after the status has been updated on the ticket.
\App\Modules\Ticket\Events\PriorityUpdated $ticket - The ticket model
$before - The old priority ID
$after - The new priority ID
Fired after the priority has been updated on the ticket.
\App\Modules\Ticket\Events\UserUpdated $ticket - The ticket model
$before - The old user ID
$after - The new user ID
Fired after the user has been updated on the ticket.
\App\Modules\Ticket\Events\BrandUpdated $ticket - The ticket model
$before - The old brand ID
$after - The new brand ID
Fired after the brand has been updated on the ticket.
\App\Modules\Ticket\Events\SubjectUpdated $ticket - The ticket model
$before - The old subject
$after - The new subject
Fired after the subject has been updated on the ticket.
\App\Modules\Ticket\Events\TagsUpdated $ticket - The ticket model
$attached - An array of ticket tag IDs added to ticket
$detached - An array of ticket tag IDs removed from ticket
Fired after the tags have been updated on the ticket.
\App\Modules\Ticket\Events\AssignedUpdated $ticket - The ticket model
$attached - An array of operator IDs now assigned to ticket
$detached - An array of operator IDs no longer assigned to ticket
Fired after the assigned operators have been updated on the ticket.
\App\Modules\Ticket\Events\WatchingUpdated $ticket - The ticket model
$attached - An array of operator IDs now watching ticket
$detached - An array of operator IDs no longer watching ticket
Fired after the watching operators have been updated on the ticket.

Ticket event listeners must be declared in the main controller constructor, and are written in the following form.


    \Event::listen(Event::class, function ($event) {
       ...
    });

See our examples below to see how they can be used in your plugin.

Example: Ticket Created

We would like to track when a ticket is created. Inside the listener, we have access to the ticket model and can fetch anything related to it as needed.


    // Register ticket created listener
    \Event::listen(\App\Modules\Ticket\Events\TicketCreated::class, function ($event) {
        // Get ticket
        $ticket = $event->ticket;

        // Get ticket user's name
        $userName = $ticket->user->formatted_name;

        // Get message text
        $message = $ticket->lastReply->purified_text;
    });

Example: Ticket Status Changed

We would like to track when the status is changed on a ticket. Inside the listener, we have access to the old and new status and can use that as needed.


    // Register ticket status updated listener
    \Event::listen(\App\Modules\Ticket\Events\StatusUpdated::class, function ($event) {
        // Get ticket
        $ticket = $event->ticket;

        // Get old and new status
        $statuses = \App\Modules\Ticket\Models\Status::findMany([ $event->before, $event->after ]);
        $oldStatus = $statuses->where('id', $event->before)->first();
        $newStatus = $statuses->where('id', $event->after)->first();

        // Use new status name
        $newStatusName = $newStatus->name;
    });

Available Model Events

If you need to track changes to non-ticket models or access ticket models before they are saved, model events must be used. Model events are split in to two, with a before and after event for each possible model change.

Before Event After Event Description
creating created Fired when a new model has been created and saved for the first time.
updating updated Fired when an existing model has been updated.
saving saved Fired when either a new model has been created or an existing model has been updated.
deleting deleted Fired when a model has been deleted.

The before events are most useful when checking what has changed and allows modify the data before it completes. Returning false in the listener for a before event would stop the operation from completing altogether. The after events are useful when you need to perform an action after the model has changed.

Model event listeners must be declared in the main controller constructor, and are written in the following form.


    Model::created(function ($item) {
       ...
    });

See our examples below to see how they can be used in your plugin.

Example: User Profile Saved

We would like to track when a user has been created or their profile has updated and send that data to another application that we're in sync with.

To simplify this, we can just post all updates to the other application even if none of the actual fields we send have actually updated, so we can use the after saved event on the User model. The thing to take care with is ensuring you only post for confirmed users and not operators who are also covered by the User model.


    // Register user saved event
    \App\Modules\User\Models\User::saved(function ($user) {
        // Only if it's a user and they're confirmed
        if (! $user->isOperator() && $user->confirmed) {
            // Get user ID from other application, or start creating new user
            // Build data in an array from model
            // Send data via API
        }
    });

Example: Email Queueing

We would like to track when an email addressed to a certain email address is about to be queued and stop that from taking place, so no email is sent out. To stop the model being saved, we have to return false.


    // Register email queue creating event
    \App\Modules\Core\Models\EmailQueue::creating(function ($email) {
        // Check if user is in the list of TO recipients
        if (in_array('test@test.com', $email->to) {
            // Stop email being sent
            return false;
        }
    });

Example: Ticket Status Changing

Model events have some benefits over the ticket events described earlier. For example, we may want to know when the status is about to change on a ticket and then perform some actions.

The updating model event allows us to see what data has changed, before it's saved to the database, using the isDirty function as demonstrated below.


    // Register ticket updating event
    \App\Modules\Ticket\Models\Ticket::updating(function ($ticket) {
        // Status changed
        if ($ticket->isDirty('status_id')) {
            $oldStatusId = $ticket->getOriginal('status_id');
            $newStatusId = $ticket->status_id;

            // If we're closing the ticket also lock it.
            if ($newStatusId == 3) {
                $ticket->locked = 1;
            }
        }
    });

While this example could also be achieved with the StatusUpdated ticket event it would require an additional SQL query to update the locked attribute on the ticket.


Next Section: Extending Templates