• Home
  • Testing
  • SAP
  • Web
  • Must Learn!
  • Big Data
  • Live Projects
  • AI
  • Blog

What is Laravel?

Laravel is a robust MVC framework for PHP created by Taylor Otwell. Since its initial release in June 2011 (version 1), it has steadily grown more and more popular in the PHP-framework sector of the development industry. A lot of this popularity can be attributed to the many developer-first minded features that it comes with stock:

Why Laravel?

Circa 2000, most PHP codes was procedural and could be found in the form of "scripts" that would have a tangled mess of spaghetti code. Even the simplest pages had no separation of concerns, and thus it was fairly easy for an application to quickly grow into a maintenance nightmare. The world needed something better...Enter PHP version 5 and a variety of PHP frameworks attempting to bring some much-needed resolution and better solutions to various web application concerns.

Since then we've seen many frameworks released that would pave the way for the popular frameworks existing and being employed today. Today, the top three would (in our opinion) be Zend Framework, Symfony and of course Laravel. Although each of these frameworks was founded on similar principles and are geared toward solving (basically) the same common problems, their key differences lie is their implementations. They each have their own quirks about how to go about solving problems. When you look at the code produced by each of them, you will see that there is a pretty solid line separating them from each other. In our humble opinion, Laravel is the best

In this beginners tutorial, you will learn.

How to Download and Install Laravel with Composer

NOTE It is assumed that you already have a copy of PHP installed on your local system. If not, you can read how to install it here

Composer is both a package and dependency manager. To install it, open up a terminal and cd into a new directory. Run this command:

curl -Ss getcomposer.org/installer | php

The results of this command will look like this:

Note For more extensive instructions on setting up Laravel, refer to the Laravel documentation here.

You will see it downloading and compiling the composer.phar script, which is what we use to install Laravel. Although there are numerous ways to set up a new Laravel application, we will do it via the Laravel composer script. To install this script, run:

composer global require laravel/installer

Which will look something like this:

This will download and install all the framework files themselves as well as all dependencies that it requires. The packages will be saved inside the vendor directory. Once it is downloaded and installed, its as easy as issuing the following command:

laravel new uploadApp

You will see something like the following output:

Composer is installating all the packages that Laravel needs in order to run. It may take a few minutes so be patient. After it is finished, run an ls -al command to take a look at what was installed.

Here is a brief breakdown of the directories in a common Laravel application:

  • app/ : This is the source folder where our application code lives. All controllers, policies, and models are inside this folder
  • bootstrap/ : Holds the application's startup script and a few class map files
  • config/ : Holds the app's configuration files. These are usually not modified directly but instead, rely on the values set up in the .env (environment) file at the root of the app
  • database/ : Houses the database files including migrations, seeds and test factories
  • public/ : Publicly accessible folder holding compiled assets and of course an index.php file
  • resources/ : Contains front-end assets such as javascript files, language files, CSS/SASS files and all templates used in the application (called blade templates)
  • routes/ : All routes in the application are inside here. There are a few different "scopes" of routes but the one we will be focusing on is the web.php file
  • storage/ : All temporary cache files used by the application, session files, compiled view scripts and log files
  • tests/ : Contains test files for the application such as unit tests and functional tests.
  • vendor/ : All dependency packages installed with composer

Now then, let's build the rest of the app and run it with a special artisan command (to save ourselves the hassle of installing and configuring a web server like Apache or nginx). The .env file contains all the configuration values that the files in the /config directory use to configure the application. Inside it, you will notice that the configuration value for various parameters used by the internals of the application.

Application design: A quick run-down of our requirements

In this online tutorial, we will be building a very simple application that will do only two things:

  1. handle file uploads from a web form
  2. displaying the previously uploaded files on a different page.

For this project, our application will be write-only, meaning that the user can only write files and view the list of files that they have uploaded. This application is extremely basic but should serve as good practice for you to start building your Laravel skills and knowledge on. Note that for the sake of brevity, I have excluded any database modeling, migrations and authentication but, in a real-world application, these are additional things you will want to consider.

Here is a list of components that we will need in order to make the application work as expected:

  • A route that will allow the outside world (internet) to use the application as well as specify the endpoint that will point to where the logic for saving the uploaded file is located
  • A controller that handles the request to response flow
  • A template that will be used to display a list of previously uploaded files and the actual upload form itself
  • A request that the controller will use to validate the data passed in from the web form

What is a Route?

A route in Laravel is basically an endpoint specified by a URI that acts as a "pointer" to some piece of functionality offered by the application. Most commonly, a route simply points to a method on a controller and also dictates which HTTP methods are able to hit that URI. A route doesn't always mean controller method, either; it could just pass execution of the application to a defined Closure or anonymous function as well.

Why use Route?

Routes are stored inside files under the /routes folder inside the project's root directory. By default, there are a few different files corresponding to the different "sides" of the application ("sides" comes from the hexagonal architecture methodology). They include:

  • web.php The public facing "browser"-based routes. These are the most common and is what gets hit by the web browser. They run through the web middleware group and also contain facilities for csrf protection (which helps defend against form-based malicious attacks and hacks) and generally contain a degree of "state" (by this I mean they utilize sessions)
  • api.php Routes that correspond to an API group and thus have the API middleware enabled by default. These routes are stateless and have no sessions or cross-request memory (one request does not share data or memory with any other request--each one is self-encapsulated).
  • console.php These routes correspond to custom artisan commands that you've created for your app
  • channels.php Registers routes for event broadcasting

The key file to be concerned with at this time is the browser-specific one, web.php . There is already one route defined by default, which is the one you hit right when navigating to the web root of your application (web root is in the public directory). We are going to need three different routes for our upload application to function:

  • /upload This will be the URI of the main page displaying our web form for uploading files.
  • /process This will be where the form located at the /upload URI posts its form-submitted data to (the form's "action")
  • /list This will list all files uploaded to the site

note The /list endpoint may not be needed if we wished to put all the logic for displaying the upload form and the list of files on a single page, however, I kept them separate for now to add a little more matter to the subject at hand.

//inside routes/web.php
Route::get('/upload', 'UploadController@upload')->name('upload');
Route::get('/download, 'UploadController@download)->name(‘download');
Route::post('/process', 'UploadController@process')->name('process');
Route::get('/list', 'UploadController@list')->name('list');

For each desired route, we list it explicitly in the routes file web.php using one of the available HTTP-specific request methods (get(), post(), put() , delete(), patch() or options() ). For a breakdown of each of these, check this out. What these methods do is specify which HTTP verbs are allowed to access that given route. If you need a route to be able to accept more than one HTTP verb (which could be the case if you are using a single page to both displays the initial data and post submitted form data), you could use the Route::any() method.

The second argument to both the Route::get() and Route::post() method (and any of the other HTTP-verb-related methods on the Route facade), is the name of a specific Controller and method housed inside that controller that gets executed upon hitting the route's endpoint with the allowed HTTP request (GET, POST, PATCH, etc.) We are using the UploadController for all three routes and have specified them in the following fashion:

The last method we call on each route is its name() function, which accepts a single string as an argument and is used to more-or-less "tag" a particular route with an easy to remember the name (in our cases, upload, process, and list). I realize it doesn't seem so great of a feature to give each route its own name when the URL is named the exact same, but it really comes in handy when you have a specific route like /users/profile/dashboard/config, which would be easier to remember as profile-admin or user-config.

A note on Facades :

  • Facades provide a "static" interface to classes that are available in the application's service container."
  • They provide a terse, memorable syntax that allows you to use Laravel's features without remembering long class names that must be injected or configured manually.

In the above route definitions, we use the Route facade instead of manually instantiating a new Illuminate/Routing/Router object and calling the corresponding methods on that object. It's just a shortcut that saves typing. Facades are used heavily throughout the Laravel framework--you can and should get more familiar with them. The docs for Facades can be found here.

What is a Controller?

A controller is the "C" in the "MVC" (Model-View-Controller) architecture, which is what Laravel is based on. A controller's job can be boiled down to this simple definition: It receives the request from the client and returns a response to the client. This is the bare-bones definition and is also the minimum requirements of any given controller. What it does in between those two things is generally considered as the "action" of the controller (or the "route's implementation"). It acts as the second point of entry to the application (the first being the request) to the client, who sends the request payload (which we will get to next) to the application, expecting some type of response (in the form of a success page, redirect, error page, or any other kind of HTTP response).

A controller does (basically) the same thing as a route definition with an anonymous function set as the "action" when that route is hit. The difference is that a controller holds up well to the separation of concerns while a route is defined inline to the actual url definition, which basically means we are coupling the route's assigned URI with the route's implementation, or the code that executes when that route is hit.

For example, the following two pieces of code will achieve the same thing:

Example #1: Route's definition & implementation inside a single method call (in the web.php routes file)

//inside routes/web.php
<?php
Route::get('/hello-world', function(Request $request) {
   $name = $request->name;
   return response()->make("<h1>Hello World! This is ".$name, 200);
});

Example #2: Route's definition is inside routes/web.php, but it's implementation resides inside the /app/Http/Controllers/HelloWorldController class

//inside routes/web.php
<?php

Route::get('/hello-world', 'HelloWorldController@index')->name('hello-world');

------------------------------------------------------------------------------------
//inside app/Http/Controllers/HelloWorldController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;

class HelloWorldController extends Controller
{
   public function index(Request $request)
   {
       $name = $request->name;
       return response()->make("<h1>Hello World! This is ".$name, 200);
   }
}

Although example #2 seems like a lot more work (which it's not--just a little more code is all), look at the benefits we gain by putting our action logic for the given "hello-world" route inside of a controller instead of with the route's definition as a callback function:

  1. Our logic is cleanly separated into its own class (separation of concerns)
  2. Our controller is set up for extension later on if we needed to add additional capabilities to it...Say maybe we wanted to add a "goodbye-world" feature… In this case we would rename the controller to a more generic "HelloController" then define two separate methods, hello() and goodbye(). We would also need to define two separate routes that mapped the /hello and /goodbye URIs to their appropriate method on the controller. This is desirable compared to fattening up a routes file with each route's implementation-defined as callback functions.
  3. Laravel has the built-in ability to cache all the route definitions in the application so that it speeds up the time it takes to find a given route (increases application performance); however, you will only be able to take advantage of that if all of your defined routes inside the application are configured using controller-specific mappings (see Example #2 above)

Let's run this command that will generate a new controller for us.

// ...inside the project's root directory:
php artisan make:controller UploadController   

Essentially, what this command does is generates a stub for a controller named "UploadController" inside of the main controller directory at /app/Http/Controllers/UploadController.php.Feel free to open up that file and take a look. It's very simple because it is only a stubbed out version of the controller, with the correct namespace path and the required classes that it extends from.

Generating the Request

Before we go forward and make a few changes to the UploadController's generated stub, I think it will make more sense to create the request class first. This is because the controller method that handles the request must type hint the request object in its signature, allowing it to automatically validate the incoming form data (as specified in the rules() method. More on that later...) For now, let's use the artisan command again to generate our request stub:

php artisan make:request UploadFileRequest

This command will generate a file called UploadFileRequest inside app/Http/Requests/UploadFileRequest. Open up the stub and take a peek... You will find it very simple, containing only two methods, authorize() and rules.

Creating the Validation Logic

Let's modify the request stub to meet the needs of our application. Modify the file so that it looks like so:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UploadFileRequest extends FormRequest
{
   /**
    * Determine if the user is authorized to make this request.
    *
    * @return bool
    */
   public function authorize()
   {
       return true;
   }

   /**
    * Get the validation rules that apply to the request.
    *
    * @return array
    */
   public function rules()
   {
       return [
           'fileName' => 'required|string',
           'userFile' => 'required|file'
       ];
   }
}

Not a whole lot of changes but notice that the authorize() method now returns true instead of false. This method decides whether or not to allow the request to go into the application. If it is set to false, it stops the request from entering the system (which would normally be a method on the controller). This would be a very handy place to put any authorization checks on the user or any other logic that may decide if the request can move forward to the controller. For now, we just return true here to allow anything and everything to use the request.

The other method, rules() is where all the magic comes into play with regard to validation. The idea is simple: return an array containing a set of rules in the form of:

'formFieldName' => 'constraints this field has separated by pipe characters (|)'

There are many different validation constraints that are supported by Laravel right out of the box. For a full list of them, check out the online documentation here. For our upload application, there are going to be two fields that are passed in via POST request from a form on the front end. The fileName parameter must be included inside the form body (i.e. required) and is used as the filename we will store the file under in the storage (this is done in the controller--we will get to it a little later). We also specify that the file name must be a string by adding a pipe character (|) and the word 'string'. Constraints are always delimited by pipes, allowing you to specify any additional criteria for the given field in a single line! What power!

The second parameter, userFile , is the actual file that the user uploads from a form on a webpage. UserFile is also required and must be a file. Note: If we were expecting the uploaded file to be an image, then we would use the image constraint instead, which would limit the file types accepted to be one of the popular image types (jpeg, png, bmp, gif or svg). Since we want to allow the user to upload any type of file, we will just stick with the file validation constraint.

That is about all there is to the request object. Its main job is to simply hold the acceptable set of criteria (constraints) that the form's body parameters must satisfy in order to proceed deeper into the application. Another thing to note is that these two fields (userFile and filename) must also be specified inside the HTML code in the form of input fields (with the field name corresponding to the name inside the request object).

You might be asking: sure this defines the characteristics of what a form request should contain, but where is the actual check of these constraints done? We will get into that next.

Modifying the Controller

Open up the app/Http/Controllers/UploadController and make the following changes to it:

<?php

namespace App\Http\Controllers;

use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\Request;
use App\Http\Requests\UploadFileRequest; //our new request class
use Illuminate\Support\Facades\Storage; 

class UploadController extends Controller
{
   /**
    * This is the method that will simply list all the files uploaded by name and provide a
    * link to each one so they may be downloaded
    *
    * @param $request : A standard form request object
    * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
    * @throws BindingResolutionException
    */
   public function list(Request $request)
   {
       $uploads = Storage::allFiles('uploads');

       return view('list', ['files' => $uploads]);
   }

   /**
    * @param $file
    * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
    * @throws BindingResolutionException
    */
   public function download($file)
   {
       return response()->download(storage_path('app/'.$file));
   }

   /**
    * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
    * @throws BindingResolutionException
    */
   public function upload()
   {
       return view('upload');
   }

   /**
    * This method will handle the file uploads. Notice that the parameter's typehint
    * is the exact request class we generated in the last step. There is a reason for this!
    *
    * @param $request : The special form request for our upload application
    * @return array|\Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|null
    * @throws BindingResolutionException
    */
   public function store(UploadFileRequest $request)
   {
       //At this point, the parameters passed into the $request (from form) are
       //valid--they satisfy each of the conditions inside the rules() method

       $filename = $request->fileName;    //parameters have already been validated
       $file = $request->file('userFile'); //that we don't need any additional isset()

       $extension = $file->getClientOriginalExtension(); //grab the file extension
       $saveAs = $filename . "." . $extension; //filename to save file under

       $file->storeAs('uploads', $saveAs, 'local'); //save the file to local folder

       return response()->json(['success' => true]); //return a success message
   }
}

So it's a fairly straightforward approach to saving the uploaded files to disk. Here is a breakdown of the upload() method above:

  • Type-hint the request class in the controller method that is doing the 'meat and potatoes' functionality so we can auto-validate the incoming data
  • Grab the file out of the (now validated) request object inside the controller method (in this case we've named it upload() but it could have also been named to a more standardized name like store()).
  • Grab the filename out of the request
  • Generate the final filename that will be used to save the file under. The getClientOriginalExtension() method simply grabs the original extension of the uploaded file.
  • Store the file to the local filesystem using its storeAs() method, passing in the named path inside the /storage directory as the 1st argument and the filename to save it under as the second.
  • Return a JSON response indicating the request was successful

The Blade Template

The last major piece to this puzzle is the blade template, which will hold all HTML, CSS, and javascript for our simple application. Here is the code--I will explain it later.

<body>
   <h1>Upload a file</h1>
   <form id="uploadForm" name="uploadForm" action="{{route('upload')}}" enctype="multipart/form-data">
       @csrf
       <label for="fileName">File Name:</label>
       <input type="text" name="fileName" id="fileName" required /><br />
       <label for="userFile">Select a File</label>
       <input type="file" name="userFile" id="userFile" required />
       <button type="submit" name="submit">Submit</button>
   </form>
   <h2 id="success" style="color:green;display:none">Successfully uploaded file</h2>
   <h2 id="error" style="color:red;display:none">Error Submitting File</h2>
   <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
   <script>
        $('#uploadForm').on('submit', function(e) {
            e.preventDefault();
            var form = $(this);
            var url = form.attr('action');
            $.ajax({
                url: url,
                type: "POST",
                data: new FormData(this),
                processData: false,
                contentType: false,
                dataType: "JSON",
                success: function(data) {
                    $("#fileName").val("");
                    $("#userFile").val("");
                }
            }).done(function() {
                $('#success').css('display', 'block');
                window.setTimeout(()=>($("#success").css('display', 'none')), 5000);
            }).fail(function() {
                $('#error').css('display', 'block');
                window.setTimeout(()=>($("#error").css('display', 'none')), 5000);
            });
        });
   </script>
</body>
</html>

Here is what our /upload page looks like:

This is a very typical example of a blade file containing an HTML form and javascript/jQuery for adding asynchronous functionality (so the page doesn't refresh). There is a basic <form> tag with no method attribute (which I'll explain in just a sec) and with a curious action attribute with the value {{route('file.upload')}}. In blade, this is what is known as a directive. A directive is just a fancy name for function--they are functions specific to blade templates that perform different operations that are common to constructing web pages and web applications. For a better understanding of all the cool shit that blade can do, check out the docs here. In the case above, we are using the route directive to generate a URL for our form submission.

Remember that we defined our routes earlier in the application inside the web.php file, specifying an easy to remember the name for each of them. The {{route()}} directive accepts a name of a route, looks it up inside the internally cached routes list, and generates a full URL based on the definition of that route in the web.php file. For this first case, we are specifying that we want the form to send its submitted data to the /process URL of our application, which is defined as a POST route.

The next weird thing you may have noticed is the @csrf tag right below the opening form tag. In blade, this tag generates a _token parameter on the form, which gets checked inside the application before the form data is allowed to be processed. This ensures that the data inside the form is of a valid origin and prevents cross-site-request-forgery attacks. For more info on this, see the docs.

After this we define our form as normal, however, do note that the names of our form parameters, userFile and fileName are the exact same as defined inside our request object. If we forgot to include an input for a given parameter that was defined in the request object (or misspelled it), the request would fail and an error would be returned, preventing the original form request from ever hitting the controller method located at UploadController@process .

Go ahead and try it out and submit a few files to the application using this form. Afterward, navigate to the /list page to see the contents of the upload folder, with the files you uploaded listed in a table:

The Bigger Picture

Let's take a step back and look at what we've done.

This diagram depicts the application as it stands right now (high-level details excluded) :

You should recall that the request object we constructed at the beginning of this tutorial should have the same parameters defined in its rules method as is on the form in the blade template (if not re-read the section "Creating the Validation Logic"). The user enters the form in a web page that is rendered via a blade template engine (this process is of course on auto-pilot so we don't have to even think about it) and submits the form. The template's jQuery code at the bottom stops the default submission (which would automatically redirect to a separate page), creates an ajax request, loads the request with the form data and uploaded the file, and sends the whole thing into the first layer of our application: the request.

The request object gets populated by associating the parameters inside the rules() method with the submitted form parameters, then validates the data according to each specified rule. If all the rules are satisfied, the request gets passed along to whatever controller method that corresponds to the values defined in the route file web.php. In this case, it is the process() method of the UploadController that does the work. Once we hit the controller, we already know that the request passed validation so we don't have to re-test if the filename given is, in fact, a string or the userFile parameter actually holds some type of file... We can continue as normal.

The controller method then grabs the validated parameters out of the request object, generates a full filename by concatenating the passed in fileName parameter with the userFile 's original extension, stores the file inside a directory in our application, then returns a simple JSON-encoded response verifying that the request was successful. The response is received by the jQuery logic, which does a few more UI-related tasks such as displaying the success (or error) message for 5 seconds then hiding it as well as clearing out the previous form entries...this is so the user knows for sure that the request was successful and can upload another file, if they wish.

Also, take note in the diagram above just where the line is drawn between the client and the server. This concept is absolutely critical for you to understand and will help you solve problems and issues you may have in the future when juggling, for example, multiple asynchronous requests that can occur at any given time. The separation is right at the boundary of the request object. The request object itself can be thought of as the "gateway" to the rest of the application... It does the initial validation and registration of form values passed in from the web browser. If they are deemed valid, then it continues to the controller. Everything before that is on the front end (the "client" literally means "on the user's computer"). The response is returned from the app back into the client side, where our jQuery code patiently listens for its arrival and does a few simple UI tasks once it receives it.

This tutorial is contributed by Jesse Griffin

 

YOU MIGHT LIKE: