Build a localized app with Laravel - Part 2 : The setup and application backend
This tutorial requires the working knowledge of the [PHP](http://php.net/) (version >= 7.1.3, [Laravel](https://laravel.com/) (version 5.6.*), and [Composer](https://getcomposer.org/doc/00-intro.md) (version >= 1.3.2), [Laravel installer](https://laravel.com/docs/5.6/installation) is installed on your computer
In this tutorial, we will build our tour guide application. We will not do anything complicated so we can rapidly get to adding multilingual support. We will build the base application and make it work without any additional language or region support. In chapters following, we will look at adding all of that support.
In the part 1, we looked at what an international application should be like. We covered some fundamental issues to consider when building an international application. We differentiated between what a multilingual application is and what a multi-regional application is. We also addressed what to do when building any of such applications.
Prerequisites
- Knowledge of PHP (version >= 7.1.3)
- Knowledge of Laravel (version 5.6.*)
- Composer is installed on your computer (version >= 1.3.2)
- Laravel installer is installed on your computer
What we are going to build
We are going to build a simple tour guide that will help users book our services. This service rendered will help users take a tour of our city. It will allow them to set a time and date they want the tour and how many persons they will come with.
The application will have a simple dashboard that serves as mission control for the administrator. It will let the admin see who booked a tour when they want the tour and where they want to tour.
Finally, it will have a page that lists all the destinations users can tour. Users will also be able to click a link to see more information about their desired destination.
Getting started
To begin, we need to create a Laravel application. We will use the Laravel installer to make it easy and fast. Run the following command:
$ laravel new tourGuide
$ cd tourGuide
It will create the Laravel application, make the .env
file and set the application. If you run php artisan serve
, your application will come up right away.
We will use SQLite
as our database. Create the SQLite
database with the following command:
$ touch database/database.sqlite
Then, update your .env
file to use SQLite
// replace
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
// with
DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/database.sqlite
Creating the models and migration files
Next thing we want to do is make the models and accompanying migrations for the database our application will use. It is important to plan this ahead of time to reduce the number of changes we will make to our application later.
For the tour guide, we want the following:
Booking information
- Destination to visit
- Date and time for visit
- Number of people to visit
- User who booked the visit
Destination information
- Name
- Image
- Description
- Location
User information
- Name
- Phone
- Country of residence
Now that we are clear on what we want to make, let us create the model, controller and migrations for them:
$ php artisan make:model Destination -mr
$ php artisan make:model Booking -mr
There is a
User
model that ships with Laravel, so we will use it. It also comes with a migration file as well. Since we only register users, we will not need aUser
controller.
Next, we edit the migration files. Open the users migration file in ./database/migrations
and replace with this:
// database/migrations/<timestamp>_create_users_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('country');
$table->string('phone');
$table->boolean('is_admin')->default(false);
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('users');
}
}
Open the destinations migration file and replace with this:
// database/migrations/<timestamp>_create_destinations_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateDestinationsTable extends Migration
{
public function up()
{
Schema::create('destinations', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('image');
$table->json('description');
$table->string('location');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('destinations');
}
}
Open the bookings migration file and replace with this:
// database/migrations/<timestamp>_create_bookings_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateBookingsTable extends Migration
{
public function up()
{
Schema::create('bookings', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('destination_id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('number_of_tourists');
$table->datetime('visit_date');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('bookings');
}
}
Our migration files are now ready. We made them mirror the information we want to store. Next, we will edit the models to make them access our database correctly.
Open the User
model in ./app
and replace with the following:
// app/User.php
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'email', 'country', 'phone', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
public function bookings()
{
return $this->hasMany(Booking::class);
}
}
Open the Destination
model and edit:
// app/Destination.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Destination extends Model
{
protected $fillable = [
'name', 'image', 'location', 'description'
];
public function bookings()
{
return $this->hasMany(Booking::class);
}
}
Open the Booking
model and edit:
// app/Booking.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Booking extends Model
{
protected $fillable = [
'user_id', 'destination_id', 'number_of_tourists', 'visit_date'
];
protected $dates = [
'created_at',
'updated_at',
'visit_date'
];
public function user()
{
return $this->belongsTo(User::class);
}
public function destination()
{
return $this->belongsTo(Destination::class);
}
}
And that is all the edits needed for our models now, we will update them as we progress.
Run the following command to create the database tables:
$ php artisan migrate
The controllers
For the controllers, we need to keep it very simple. In subsequent chapters, we will extend the controllers to serve content based on the language a user requests. All the controllers can be found in ./app/Http/Controllers
directory.
The first thing we want to do is edit the RegisterController
to include the phone
and country
fields. Open Auth/RegisterController.php
and edit:
<?php
namespace App\Http\Controllers\Auth;
[...]
class RegisterController extends Controller
{
[...]
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'phone' => 'required|string|unique:users',
'country' => 'required|string|max:255',
'password' => 'required|string|min:6|confirmed',
]);
}
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'phone' => $data['phone'],
'country' => $data['country'],
'password' => Hash::make($data['password']),
]);
}
}
Storing countries as a string means each user will type in their country. This is not very efficient because it will leave our database with a lot of redundant data. For this tutorial, we will ignore that since we will not be going live with it. If you intend to go live with it, please consider creating a table with countries and link them using their
id
. Learn about database normalization.
Destination controller
In our destination controller, we want to list all the destinations we tour. We also want to give users the ability to click on a destination and see more information on it. So, we are going to create a few methods to return the data we want.
Open the DestinationController
and replace with the following:
// app/Http/Controllers/DestinationController.php
<?php
namespace App\Http\Controllers;
use App\Destination;
use Illuminate\Http\Request;
class DestinationController extends Controller
{
public function index()
{
$destinations = Destination::all();
return view('destination.index', compact('destinations'));
}
public function show(Destination $destination)
{
return view('destination.show', compact('destination'));
}
}
We will seed our destination table so there will be no need to create methods for creating, updating or deleting destination records. This is to keep it simple.
Booking controller
The booking controller should show us what users booked and also help users book tours. Open the controller and replace with the following:
// app/Http/Controllers/BookingController.php
<?php
namespace App\Http\Controllers;
use App\Booking;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Carbon\Carbon;
use Auth;
class BookingController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('admin')->only(['index']);
}
public function index()
{
$bookings = Booking::with(['destination','user'])->get();
return view('booking.index',compact('bookings'));
}
public function create(\App\Destination $destination)
{
return view('booking.create',compact('destination'));
}
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'destination_id' => 'required|integer',
'number_of_tourists' => 'required|integer',
'visit_date' => 'required'
]);
$validator->validate();
$input = $request->only(['destination_id', 'number_of_tourists', 'visit_date']);
$input['user_id'] = Auth::id();
$input['visit_date'] = Carbon::createFromFormat('m/d/Y',$input['visit_date'])->toDateTimeString();
$booking = Booking::create($input);
return redirect('/home');
}
public function userPage()
{
$bookings = Auth::user()->bookings()->with('destination')->get();
return view('booking.userpage', compact('bookings'));
}
}
Our booking controller has a constructor that defines a middleware for checking each request to it. We defined the auth
middleware to ensure that only logged in users can access the pages linked to this controller.
We then defined an admin
middleware to be sure ONLY admin users can see the page tied to the index
method. We will define the admin middleware below.
Middlewares are called in Laravel in the order which they are placed like first, second, … If the execution of a middleware is dependent on say
auth
, you have to place theauth
middleware before it. This also applies to using route groups and adding middlewares to it.
In our index
method, we are fetching all bookings and eager loading the users and destinations tied to them. This is a good option since we intend to use the data. It saves us some time with the number of queries we have to run and will make our application faster.
The create
method returns the form for making a booking with information on the destination the user had clicked on from the previous page.
The store
method stores the booking a user made. Because we set bootstrap datepicker to return the date in a format that is easy for users to read, we are doing a second conversion to a format that our application stores.
The userPage
method returns the bookings the logged in user made with the destination information eager loaded.
Create the authentication
We will use Laravel’s authentication scaffolding. To generate it, run the following command:
$ php artisan make:auth
This will publish the authentication routes in routes/web.php
and also create the view files for different authentication actions like registration, login and more. They are all connected to the respective controllers handling them so we will not worry about setting that up anymore.
When we use the scaffolding, it makes the /home
route, which is where logged in users are redirected to. We can change this to what we want in the LoginController
. For this app, we are going to set the route as the default for regular users and redirect admin users.
First, open .app/Http/Controllers/Auth/LoginController.php
and add the following:
[...]
use Illuminate\Http\Request;
class LoginController extends Controller
{
[...]
protected function authenticated(Request $request, $user)
{
if ($user->is_admin) {
return redirect('/dashboard');
}
}
}
Now, when an admin user logs in, they get redirected to dashboard
. We are going to make a middleware to restrict access to the dashboard page.
Making the middleware for the admin check
Middleware in Laravel provides a convenient mechanism for filtering HTTP requests entering our application. This middleware is going to check if a logged in user has an administrator account or not. This is important since we use a single users table for both regular users and administrators.
Run the following command to create the middleware:
$ php artisan make:middleware IsAdmin
Then open the IsAdmin
middleware file in ./app/Http/Middleware
and add the following:
<?php
namespace App\Http\Middleware;
use Closure;
class IsAdmin
{
public function handle($request, Closure $next)
{
if (!$request->user()->is_admin) {
abort(404);
}
return $next($request);
}
}
Next, we register the route in ./app/Http/Kernel.php
:
protected $routeMiddleware = [
'admin' => \App\Http\Middleware\IsAdmin::class,
[...]
];
We used this middleware in our booking controller to protect the index
method. This middleware will only allow admin users to see the page containing all bookings while other non-admin users are directed to a lovely 404 page.
Conclusion
We have looked at what an international application is and different things to consider when making the application. We also got started on a simple application for tourists to book a tour guide service.
In the part 3, we will make the views for all of our pages. We will make the styles for multi-language support and make the pages as well. Then, we can proceed to adding content in multiple languages and see how to show content based on the users language.
The source code to the application in this article is available on GitHub.
6 August 2018
by Fisayo Afolayan