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.
Table of Contents
What we will be building will be minimal, but it will have the major features of support ticket application.
Application Requirements and Flow
- And authenticated user can open support tickets
- Upon opening the tickets, an email will be sent to the user along with the details of the support ticket opened
- Subsequently, mails will be sent to the user as the customer support staff or admin response to the ticket
- The user can also respond to the ticket he/she opened by commenting on the ticket
- The admin or the customer support staff can also mark a ticket as resolved
- 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.
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:
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.
Like this article? Follow @ammezie on Twitter