Build A Support Ticket Application With Laravel – Part 1

Learn how to build a support ticket application with Laravel

Update (August 15, 2016): Add link to part 2 of the tutorial and add the flash view file. Update (August 08, 2016): User needs to be logged in to create a ticket Update (August 08, 2016): Corrected some errors pointed out on the comments. Update (August 06, 2016): Add Use App\CategoryLaravel is currently the most starred PHP framework on GitHub and also the most popular PHP framework. Thanks to Taylor Otwell and the Laravel community, many companies and people are building awesome applications with this framework. In this two part tutorial, I'll show you how to build a robust application using the Laravel framework.

What We Will Be Building

We're going to be building a support ticket application. A support ticket application provides a medium for customers to lodge issues they face using a particular organization's service/product by opening a support ticket which the organization's help desk.

What we will be building will be minimal, but it will have the major features of support ticket application.

Application Requirements and Flow

  1. And authenticated user can open support tickets
  2. Upon opening the tickets, an email will be sent to the user along with the details of the support ticket opened
  3. Subsequently, mails will be sent to the user as the customer support staff or admin response to the ticket
  4. The user can also respond to the ticket he/she opened by commenting on the ticket
  5. The admin or the customer support staff can also mark a ticket as resolved
  6. Once a ticket is marked as closed, the user who opened the ticket will be notified by email on the status of the ticket.

Below is an image of the final output of what we will be building. Support Ticket Application - Final Output

Let's Get Started

First let's install Laravel, we will be using the latest version of Laravel which is 5.2.39 at the time of this tutorial. I'll make use of the Laravel installer, but you can use your preferred method.

laravel new support-ticket

This will install Laravel in a directory named support-ticket. You can test to make sure the installation was successful by running:

cd support-ticket

php artisan serve

You should see the default Laravel welcome page with the text Laravel 5.

The rest of this tutorial assumes you are already in the project directory which is support-ticket.

Next, we need to setup the ability for users to register and login to the application. Lucky for us, Laravel provides authentication out of the box. To make use of this authentication, run:

php artisan make:auth

This will create all of the routes and views you need for authentication. Now there is a register and login page with which to authenticate the users.

Database Setup

Now we need a database to hold our data. Open the .env file and fill it with your database details.

DB_DATABASE=support-ticket
DB_USERNAME=homestead
DB_PASSWORD=secret

Next we need a users table. When you install Laravel, it will create for you a User model and some migration files to create the users and password resets tables respectively. But before we run the migrations, let's modify the users migration file a bit to suit our application.

// users table migration showing only the up() schemas with our modifications 
Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('email')->unique();
    $table->string('password');
    $table->integer('is_admin')->unsigned()->default(0);
    $table->rememberToken();
    $table->timestamps();
});

I have added an is_admin column which will help us track if a particular user is an admin or not. Go ahead and run the migrations:

php artisan migrate

Now users can register and login to the application.

Creating A User Account

Only registered users can make use of our Support Ticket Application . Having said that, register as a new user and login to create a ticket.

Opening A Ticket

Having taken care of users authentication, we move on to allow users create new tickets. First we need a tickets table to hold the tickets that users will be creating and also a model for them.

php artisan make:model Ticket -m

The above command will create a Ticket model and also a migration to create tickets table. Open the create tickets table migration file and update it with:

// tickets table migration showing only the up() schemas with our modifications 
Schema::create('tickets', function (Blueprint $table) {
    $table->increments('id');
    $table->integer('user_id')->unsigned();
    $table->integer('category_id')->unsigned();
    $table->string('ticket_id')->unique();
    $table->string('title');
    $table->string('priority');
    $table->text('message');
    $table->string('status');
    $table->timestamps();
});

The migration file is pretty straightforward, we have a user_id column that will hold the id of the user that created the ticket, a category_id column to indicate the category the ticket belongs, ticket_id column which hold a unique random string that will be used to refer to the ticket, a title column, a priority column, a message column and a status column which indicate whether a ticket is open or closed.

We also need a categories table to hold our various categories:

php artisan make:model Category -m

As above, this will create a Category model and migration to create categories table. Open the create categories table migration file and update it with:

Schema::create('categories', function (Blueprint $table) {
    $table->increments('id');
       $table->string('name');
    $table->timestamps();
});

Now run the migrations.

php artisan migrate

Open the Ticket model and update it with:

protected $fillable = [
    'user_id', 'category_id', 'ticket_id', 'title', 'priority', 'message', 'status'
];

This tells Laravel that the columns specified can be mass assigned. We also need to do this with our Category model.

Open the Category model and update it with:

protected $fillable = ['name'];

Ticket To Category Relationship

Let's set the relationship between the Ticket model and the Category model. A ticket can belong to a category, while a category can have many tickets. This is a one to many relationship and we will use Eloquent to setup the relationship.

Open the Ticket model and add the following code to it:

// Ticket.php file

public function category()
{
    return $this->belongsTo(Category::class);
}

Now let's edit the Category model in the same manner:

// Category.php file

public function tickets()
{
    return $this->hasMany(Ticket::class);
}

Adding Categories

For now we need to manually populate the categories table with some data. I'll be using Laravel artisan's tinker.

If you are new to Laravel artisan's Tinker, you check out this tutorial an Tinker with the Data in Your Laravel Apps with Php Artisan Tinker. To start Tinker run:

php artisan tinker

Once in the Tinker environment, enter the following:

$category = new App\Category
$category->name = "Technical"
$category->save()

This will create a Technical category inside the categories table. You can repeat the process above to add more categories.

Next we need a controller that will contain the logic for opening a ticket.

php artisan make:controller TicketsController

This will create a TicketsController. Open it and add:

// TicketsController.php file

// Remember to add this line 
use App\Category;

public function create()
{
    $categories = Category::all();

    return view('tickets.create', compact('categories'));
}

The TicketsController now has a create() function which will get all the categories created and pass them along to a view file. Before we move further, lets create the routes that will handle opening a new ticket.

// routes.php file

Route::get('new_ticket', 'TicketsController@create');
Route::post('new_ticket', 'TicketsController@store');

The first route /new_ticket will show the form to open a new ticket while the second route will call the store() on TicketsController which will do the actual storing of the ticket in the database.

The create() from above return a view file tickets.create which we have yet to create. Let's go on and create the file.

Create a new folder named tickets in the views directory and inside the tickets folder, create a new file named create.blade.php. Open the view file and update it with:

// create.blade.php file

@extends('layouts.app')

@section('title', 'Open Ticket')

@section('content')
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <div class="panel panel-default">
                <div class="panel-heading">Open New Ticket</div>

                <div class="panel-body">
                    @include('includes.flash')

                    <form class="form-horizontal" role="form" method="POST" action="{{ url('/new_ticket') }}">
                        {!! csrf_field() !!}

                        <div class="form-group{{ $errors->has('title') ? ' has-error' : '' }}">
                            <label for="title" class="col-md-4 control-label">Title</label>

                            <div class="col-md-6">
                                <input id="title" type="text" class="form-control" name="title" value="{{ old('title') }}">

                                @if ($errors->has('title'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('title') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('category') ? ' has-error' : '' }}">
                            <label for="category" class="col-md-4 control-label">Category</label>

                            <div class="col-md-6">
                                <select id="category" type="category" class="form-control" name="category">
                                    <option value="">Select Category</option>
                                    @foreach ($categories as $category)
                        <option value="{{ $category->id }}">{{ $category->name }}</option>
                                    @endforeach
                                </select>

                                @if ($errors->has('category'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('category') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('priority') ? ' has-error' : '' }}">
                            <label for="priority" class="col-md-4 control-label">Priority</label>

                            <div class="col-md-6">
                                <select id="priority" type="" class="form-control" name="priority">
                                    <option value="">Select Priority</option>
                                    <option value="low">Low</option>
                                    <option value="medium">Medium</option>
                                    <option value="high">High</option>
                                </select>

                                @if ($errors->has('priority'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('priority') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('message') ? ' has-error' : '' }}">
                            <label for="message" class="col-md-4 control-label">Message</label>

                            <div class="col-md-6">
                                <textarea rows="10" id="message" class="form-control" name="message"></textarea>

                                @if ($errors->has('message'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('message') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    <i class="fa fa-btn fa-ticket"></i> Open Ticket
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
@endsection

Flash View File

Create a file named flash.blade.php in the includes folder inside the views folder. Paste the code snippets below into it:

// flash.blade.php

@if (session('status'))
    <div class="alert alert-success">
        {{ session('status') }}
    </div>
@endif

We will be making use of the default app layout provided by Laravel with some modifications. Now visit the route /new_ticket and you should see a page like the image below: Open New Ticket

To handle the actual saving of the ticket to the database, open TicketsController and add the store() to it.

// TicketsController.php
// Remember to add the lines below
// use App/Ticket;
// use App/Mailers/AppMailer;
// use Illuminate\Support\Facades\Auth;

public function store(Request $request, AppMailer $mailer)
{
    $this->validate($request, [
            'title'     => 'required',
            'category'  => 'required',
            'priority'  => 'required',
            'message'   => 'required'
        ]);

        $ticket = new Ticket([
            'title'     => $request->input('title'),
            'user_id'   => Auth::user()->id,
            'ticket_id' => strtoupper(str_random(10)),
            'category_id'  => $request->input('category'),
            'priority'  => $request->input('priority'),
            'message'   => $request->input('message'),
            'status'    => "Open",
        ]);

        $ticket->save();

        $mailer->sendTicketInformation(Auth::user(), $ticket);

        return redirect()->back()->with("status", "A ticket with ID: #$ticket->ticket_id has been opened.");
}

The store() accepts two arguments, $request variable of type Request and $mailer variable of type AppMailer which we have yet to create. The method first sets some form validations rules that must be met before moving forward. Upon successful form validation, a new the ticket is created and an email containing the ticket details is sent to the user (more on this below) and finally the user is redirected back with a success message.

Sending Ticket Information Mail

I decided to create a separate class that will handle sending of mails since we might be sending some a couple of emails. Under the app directory, create a new folder named mailers and inside the mailer folder create a new file named AppMailer.php. Open and update the file with:

// AppMailer.php file

<?php
namespace App\Mailers;

use App\Ticket;
use Illuminate\Contracts\Mail\Mailer;

class AppMailer {
    protected $mailer; 
    protected $fromAddress = 'support@supportticket.dev';
    protected $fromName = 'Support Ticket';
    protected $to;
    protected $subject;
    protected $view;
    protected $data = [];

    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public function sendTicketInformation($user, Ticket $ticket)
    {
        $this->to = $user->email;
        $this->subject = "[Ticket ID: $ticket->ticket_id] $ticket->title";
        $this->view = 'emails.ticket_info';
        $this->data = compact('user', 'ticket');

        return $this->deliver();
    }

    public function deliver()
    {
        $this->mailer->send($this->view, $this->data, function($message) {
            $message->from($this->fromAddress, $this->fromName)
                    ->to($this->to)->subject($this->subject);
        });
    }

You can see the sendTicketInformation() that store() from above called. It basically just prepares the email to be sent to the user that opened the ticket. The deliver() function does the actual sending of the email.

We need one more view file. This view file will be inside the emails folder (which does not exist yet) in the view directory. Go on and create ticket_info.blade.php and add:

// ticket_info.blade.php file

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Suppor Ticket Information</title>
</head>
<body>
    <p>
        Thank you {{ ucfirst($user->name) }} for contacting our support team. A support ticket has been opened for you. You will be notified when a response is made by email. The details of your ticket are shown below:
    </p>

    <p>Title: {{ $ticket->title }}</p>
    <p>Priority: {{ $ticket->priority }}</p>
    <p>Status: {{ $ticket->status }}</p>

    <p>
        You can view the ticket at any time at {{ url('tickets/'. $ticket->ticket_id) }}
    </p>

</body>
</html>

This is the email that will be sent to the user once a ticket is created.

Before testing this out in the browser, remember to configure your mail settings. For the purpose of this tutorial, we won't be sending actual emails. Instead we will just log them. So open .env file and update it as below:

MAIL_DRIVER=log

Now hit the browser and try opening a new ticket, you should see the mail content logged in the laravel.log file once the ticket is created.

Conclusion

So far, we have been able to setup our application and created our first ticket. In the next post I will cover commenting on ticket and marking ticket as closed. If you have any questions about the tutorial, let me know in the comments below. And be sure to check out part two here.

Chimezie Enyinnaya

web developer [PHP Laravel VueJS|AngularJS] | movie lover | I run http://openlaravel.com | blogs at http://itoocode.com