Throughout the book, we will be using the Laravel PHP framework to build different types of web projects.
In this chapter, we'll see how to build a URL Shortener website with the basics of Laravel framework. The covered topics include:
Creating a database and migrating our URL Shortener's table
Creating our form
Creating our Link model
Saving data to the database
Getting individual URL from the database and redirecting
Migrations are like version control for your application's database. They permit a team (or yourself) to modify the database schema, and provide up-to-date information on the current schema state. To create and migrate your URL Shortener's database, perform the following steps:
First of all, we have to create a database, and define the connection information to Laravel. To do this, we open the
database.php
file underapp/config
, and then fill the required credentials. Laravel supports MySQL, SQLite, PostgreSQL, and SQLSRV (Microsoft SQL Server) by default. For this tutorial, we will be using MySQL.We will have to create a MySQL database. To do this, open your MySQL console (or phpMyAdmin), and write down the following query:
CREATE DATABASE urls
The previous command will generate a new MySQL database named
urls
for us. After having successfully generated the database, we will be defining the database credentials. To do this, open yourdatabase.php
file underapp/config
. In that file, you will see multiple arrays being returned with database definitions.The
default
key defines what database driver to be used, and each database driver key holds individual credentials. We just need to fill the one that we will be using. In our case, I've made sure that the default key's value ismysql
. To set the connection credentials, we will be filling the value of themysql
key with our database name, username, and password. In our case, since we have adatabase
namedurls
, with theusername
asroot
and without a password, ourmysql
connection settings in thedatabase.php
file will be as follows:'mysql' => array( 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'database', 'username' => 'root', 'password' => '', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', ),
Tip
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
Now, we will be using the Artisan CLI to create migrations. Artisan is a command-line interface specially made for Laravel. It provides numerous helpful commands to help us in development. We'll be using the following
migrate:make
command to create a migration on Artisan:php artisan migrate:make create_links_table --table=links --create
The command has two parts:
The first is
migrate:make create_links_table
. This part of the command creates a migration file which is named something like2013_05_01_194506_create_links_table.php
. We'll examine the file further.The second part of the command is
--table=links --create
.The
--table=links
option points to the database name.The
--create
option is for creating the table on the database server to which we've given the--table=links
option.
As you can see, unlike Laravel 3, when you run the previous command, it will create both the migrations table and our migration. You can access the migration file under
app/database/migrations
, having the following code:<?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateLinksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('links', function(Blueprint $table) { $table->increments('id'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('links'); } }
Let's inspect the sample migration file. There are two public functions which are declared as
up()
anddown()
. When you execute the followingmigrate
command, the contents of theup()
function will be executed:php artsian migrate
This command will execute all of the migrations and create the
links
table in our case.We can also roll back to the last migration, like it was never executed in the first place. We can do this by using the following command:
php artisan migrate:rollback
In some cases, we may also want to roll back all migrations we have created. This can be done with the following command:
php artisan migrate:reset
While in the development stages, we may forget to add/remove some fields, or even forget to create some tables, and we may want to roll back everything and remigrate them all. This can be done using the following command:
php artisan migrate:refresh
Now, let's add our fields. We've created two additional fields called
url
andhash
. Theurl
field will hold the actual URL, to which the URL present in thehash
field will be redirected. Thehash
field will hold the shortened version of the URL that is present in theurl
field. The final content of the migration file is as shown in the following code:<?php use Illuminate\Database\Migrations\Migration; class CreateLinksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('links', function(Blueprint $table) { $table->increments('id'); $table->text('url'); $table->string('hash',400); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('links'); } }
Now let's make our first form view.
Save the following code as
form.blade.php
underapp/views
. The file's extension isblade.php
because we will be benefiting from Laravel 4's built-in template engine called Blade . There may be some methods you don't understand in the form yet, but don't worry. We will cover everything regarding this form in this chapter.<!DOCTYPE html> <html lang="en"> <head> <title>URL Shortener</title> <link rel="stylesheet" href="/assets/css/styles.css" /> </head> <body> <div id="container"> <h2>Uber-Shortener</h2> {{Form::open(array('url'=>'/','method'=>'post'))}} {{Form::text('link',Input::old('link'),array('placeholder'=>'Insert your URL here and press enter!'))}} {{Form::close()}} </div> </body> </html>
Now save the following codes as
styles.css
underpublic/assets/css
:div#container{padding-top:100px; text-align:center; width:75%; margin:auto; border-radius:4px} div#container h2{font-family:Arial,sans-serif; font-size:28px; color:#555} div#container h3{font-family:Arial,sans-serif; font-size:28px} div#container h3.error{color:#a00} div#container h3.success{color:#0a0} div#container input{display:block; width:90%; float:left; font-size:24px; border-radius:5px} div#error,div#success{border-radius:3px; display:block; width:90%; padding:10px} div#error{background:#ff8080; border:1px solid red} div#success{background:#80ff80; border:1px solid #0f0}
This code will produce you a form that looks like the following screenshot:
As you can see, we have used a CSS file to tidy up the form a bit, but the actual part of the form is at the bottom of the
View
file, insidediv
with the ID of the container.We have used the Laravel's built-in
Form
class to generate a form, and we have used theold()
method of theInput
library. Now let's dig the code:Form::open()
: It creates a<form>
opening tag. Within the first provided parameter, you can define how the form is sent, and where it is going to be sent. It can be a controller's action, a direct URL, or a named route.Form::text()
: It creates an<input>
tag with type as text. The first parameter is the name of the input, the second parameter is the value of the input, and within the array given in the third parameter, you can define assets and other attributes of the<input>
tag.Input::old()
: It will return the old input from a form, after the form is returned with the inputs. The first parameter is the name of the old input submitted. In our case, if the form is returned after submission (for example, if the form validation fails), the text field will be filled with our old input and we can reuse it for later requests.
To benefit from Laravel's ORM class called Eloquent
, we need to define a model. Save the following code as Link.php
under app/models
:
<?php class Link extends Eloquent { protected $table = 'links'; protected $fillable = array('url','hash'); public $timestamps = false; }
The Eloquent model is quite easy to understand.
The variable
$table
is used to define the table name for the model, but it's not compulsory. Even if we don't define this variable, it would use the plural form of the model name as a database table name. For example, if the model name was post, it would look for the post's table as default. This way, you can use any model names for the tables.The protected
$fillable
variable defines what columns can be (mass) created and updated. Laravel 4 blocks the mass-assignment of the values of all the columns withEloquent
by default. This way, for example, if you have ausers
table, and you are the only user/administrator, the mass-assignment protects your database from another user being added.The public
$timestamps
variable checks whether the model should try setting the timestampscreated_at
andupdated_at
by default, while creating and updating the query respectively. Since we don't need these features for our chapter, we will disable this by setting the value tofalse
.
We now need to define this view to show whether we can navigate to our virtual host's index page. You can do this either from the controllers defined in routes.php
, or from routes.php
directly. Since our application is small, defining them directly from routes.php
should suffice. To define this, open the routes.php
file under the app
folder and add the following code:
Route::get('/', function() { return View::make('form'); });
Note
If you already have a section starting with Route::get('/', function()
, you should replace that section with the previous code.
Laravel can listen get
, post
, put
, and delete
requests. Since our action is a get
action (because we will be navigating through the browser without posting), our route type will be get
, and because we want to show the view on the root page, our first parameter of the Route::get()
method will be /
, and we wrap a closure function as the second parameter to define what we want to do. In our case, we will be returning form.blade.php
placed under app/views
that we had generated before, so we just type return View::make('form')
. This method returns the form.blade.php
view from the views
folder.
Now we need to write a route that will have to listen to our post
request. For this, we open our routes.php
file under the app
folder and add the following code:
Route::post('/',function(){ //We first define the Form validation rule(s) $rules = array( 'link' => 'required|url' ); //Then we run the form validation $validation = Validator::make(Input::all(),$rules); //If validation fails, we return to the main page with an error info if($validation->fails()) { return Redirect::to('/') ->withInput() ->withErrors($validation); } else { //Now let's check if we already have the link in our database. If so, we get the first result $link = Link::where('url','=',Input::get('link')) ->first(); //If we have the URL saved in our database already, we provide that information back to view. if($link) { return Redirect::to('/') ->withInput() ->with('link',$link->hash); //Else we create a new unique URL } else { //First we create a new unique Hash do { $newHash = Str::random(6); } while(Link::where('hash','=',$newHash)->count() > 0); //Now we create a new database record Link::create(array('url' => Input::get('link'),'hash' => $newHash )); //And then we return the new shortened URL info to our action return Redirect::to('/') ->withInput() ->with('link',$newHash); } } });
Using the post
action function that we've coded now, we will be validating the user's input with the Laravel's built-in Validation
class. This class helps us prevent invalid inputs from getting into our database.
We first define a $rules
array to set the rules for each field. In our case, we want the link to have a valid URL format. Then we can run the form validation using the Validator::make()
method and assign it to the $validation
variable. Let's understand the parameters of the
Validator::make()
method:
The first parameter of the
Validator::make()
method takes an array of inputs and values to be validated. In our case, the whole form has only one field called link, so we've put theInput::all()
method, which returns all the inputs from the form.The second parameter takes the validation rules to be checked. The stored
$validation
variable holds some information for us. For example, we can check whether the validation has failed or passed (using$validation->fails()
and$validation->passes()
). These two methods return Boolean results, so we can easily check if the validation has passed or failed. Also, the$validation
variable holds a methodmessages()
, which contains the information of a failed validation. And we can catch them using$validation->messages()
.
If the form validation fails, we redirect the user back to our index page (return Redirect::to('/')
), which holds the URL shortener form, and we return some flash data back to the form. In Laravel, we do this by adding the withVariableName
object to the redirected page. Using with
is mandatory here, which will tell Laravel that we are returning something additional. We can do this for both redirecting and making views. If we are making views and showing some content to the end user, those withVariableName
will be variables, and we can call them directly using $VariableName
, but if we are redirecting to a page with the withVariableName
object, VariableName
will be a flash session data, and we can call it using the Session
class (Session::get('VariableName')
).
In our example, to return the errors, we used a special method withErrors($validation)
, instead of returning $validation->messages()
. We could also return using that, but the $errors
variable is always defined on views, so we can use our $validation
variable as a parameter with withErrors()
directly. The withInput()
method is also a special method, which will return the results back to the form.
//If validation fails, we return to the main page with an error info if($validation->fails()) { return Redirect::to('/') ->withInput() ->withErrors($validation); }
If the user forgets one field in the form, and if the validation fails and shows the form again with error messages, using the withInput()
method, the form can be filled with the old inputs again. To show these old inputs in Laravel, we use the old()
method of the Input
class. For example, Input::old('link')
will return us the old input of the form field named link
.
To return the error message back to the form, we can add the following HTML code into form.blade.php
:
@if(Session::has('errors')) <h3 class="error">{{$errors->first('link')}}</h3> @endif
As you can already guess, Session::has('variableName')
returns a Boolean value to check whether there is a variable name for the session. Then, with the first('formFieldName')
method of Laravel's Validator
class, we are returning the first error message of a form field. In our case, we are showing the first error message of our link
form field.
The else
part of the validation checking part that is executed when the form validation is completed successfully in our example, holds the further processing of the link. In this section, we will perform the following steps:
Checking whether the link is already in our database.
If the link is already in our database, returning the shortened link.
If the link is not present in our database, creating a new random string (the hash that will be in our URL) for the link.
Creating a new record in our database with the provided values.
Returning the shortened link back to the user.
Here's the first part of our code:
// Now let's check if we already have the link in our database. If so, we get the first result $link = Link::where('url','=',Input::get('link')) ->first();
First, we check if the URL is already present in our database using the
where()
method of Fluent Query Builder , and get the first result via the methodfirst()
, and assign it to the$link
variable. You can use the Fluent query methods along with the Eloquent ORM easily. If this confuses you, don't worry, we will cover this further in the later chapters.This is the next part of our controller method's code:
//If we have the URL saved in our database already, we provide that information back to view. if($link) { return Redirect::to('/') ->withInput() ->with('link',$link->hash);
If we have the URL saved in our database, the
$link
variable will hold the object of our link's information taken from the database. So with a simpleif()
clause, we can check if there is a result. If there is a result returned, we can access it using$link->columnname
.In our case, if the query has a result, we redirect the inputs and the link back to the form. As we've used here, the
with()
method can also be used with two parameters instead of using a camel case—withName('value')
is exactly the same aswith('name','value')
. So, we can return the hash code with a flash data named linkwith('link',$link->hash)
. To show this, we can add the following code to our form:@if(Session::has('link')) <h3 class="success"> {{Html::link(Session::get('link'),'Click here for your shortened URL')}}</h3> @endif
The
Html
class helps us write HTML codes easily. Thelink()
method requires the following two parameters:The optional third parameter has to be an array, holding attributes (such as class, ID, and target) as a two-dimensional array.
The following is the next part of our code:
//Else we create a new unique URL } else { //First we create a new unique Hash do { $newHash = Str::random(6); } while(Link::where('hash','=',$newHash)->count() > 0);
If there is no result (the else clause of the variable), we are creating a six-character-long alphanumeric random string with the
Str
class'srandom()
method and checking it each time to make sure that it is a unique string, using PHP's own do-while statement. For real world application, you can use an alternative method to shorten, for example, converting an entry in the ID column to base_62 and using it as a hash value. This way, the URL would be cleaner, and it's always a better practice.This is the next part of our code:
//Now we create a new database record Link::create(array( 'url' => Input::get('link'), 'hash' => $newHash ));
Once we have a unique hash, we can add the link and the hash values to the database with the
create()
method of the Laravel's Eloquent ORM. The only parameter should be a two-dimensional array, in which the keys of the array are holding the database column names, and the values of the array are holding the values to be inserted as a new row.In our case, the
url
column has to have thelink
field's value that came from the form. We can catch these values that came from thepost
request using Laravel'sInput
class'sget()
method. In our case, we can catch the value of thelink
form field that came from thepost
request (which we would catch using the spaghetti code$_POST['link']
) usingInput::get('link')
, and return the hash value to the view as we did earlier.This is the final part of our code:
//And then we return the new shortened URL info to our action return Redirect::to('/') ->withInput() ->with('link',$newHash);
Now, at the output, we're redirected to
oursite.dev/hashcode
. There is a link stored in the variable$newHash
; we need to catch this hash code and query our database, and if there is a record, we need to redirect to the actual URL.
Now, in the final part of our first chapter, we need to get the hash
part from the generated URL, and if there is a value, we need to redirect it to the URL which is stored in our database. To do this, add the following code at the end of your routes.php
file under the app
folder:
Route::get('{hash}',function($hash) { //First we check if the hash is from a URL from our database $link = Link::where('hash','=',$hash) ->first(); //If found, we redirect to the URL if($link) { return Redirect::to($link->url); //If not found, we redirect to index page with error message } else { return Redirect::to('/') ->with('message','Invalid Link'); } })->where('hash', '[0-9a-zA-Z]{6}');
In the previous code, unlike other route definitions, we added curly brackets around the name hash
, which tells Laravel that it's a parameter; and with the where()
method we defined how the name parameter will be. The first parameter is the name of the variable (which is hash
in our case), and the second parameter is a regular expression that will filter the parameter. In our case, the regular expression filters an exact alphanumeric string that is six-characters long. This way, we can filter our URLs and secure them from start, and we won't have to check if the url
parameter has something we don't want (for example, if alphabets are entered instead of numbers in the ID column). To get individual URL from the database and redirect, we perform the following steps:
In the
Route
class, we first make a search query, as we did in the earlier section, and check if we have a link with the given hash from a URL in our database, and set it to a variable called$link
.//First we check if the hash is from an URL from our database $link = Link::where('hash','=',$hash) ->first();
If there is a result, we redirect the page to the
url
column of our database, which has the link to which the user should be redirected.//If found, we redirect to the link if($link) { return Redirect::to($link->url); }
If there is no result, we redirect the user back to our index page using the
$message
variable, which holds the valueInvalid Link
.//If not found, we redirect to index page with error message } else { return Redirect::to('/') ->with('message','Invalid Link'); }
To show the
Invalid Link
message in the form, add the following code in yourform.blade.php
file placed underapp/views
:@if(Session::has('message')) <h3 class="error">{{Session::get('message')}}</h3> @endif
In this chapter, we have covered the basic usage of Laravel's routes, models, artisan commands, and database drivers by making a simple URL shortener website. Once you've followed this chapter, you can now create database tables with migrations, write simple forms with the Laravel Form Builder Class, validate these forms with the Validation
class, and process these forms and insert new data to the table(s) with the Fluent Query Builder or Eloquent ORM. In the next chapter, we'll cover the advanced usage of these awesome features.