Laravel 101 - Routing and Response

When developing your application, you just want to focus on development, and not bother with configuring your web server. So, Artisan actually lets you test you application using PHP's built-in web server. To start it, run

$ php artisan serve

This will start up your application on localhost:8000. When you navigate to it, you'll see (look closely!) a blank 'Laravel' screen.

The Laravel Welcome Screen

So what's actually happening behind the scenes?

Handling Requests

The serve command is registered with the registerServeCommand() method of the Illuminate\Foundation\Providers\ArtisanServiceProvider class. This simply returns a ServeCommand object and when it's fire() method is ran, will execute our server.php file, located at the application root directory.

The important line inside server.php is

require_once __DIR__.'/public/index.php';

The public directory contains all the public-facing assets and file, and it is where we normally point our web server to. Inside this directory, there's a index.php file which will receive all requests from all clients.

Long story short, Laravel takes the protocol and path from the request URL, and pass it to the app/Http/routes.php file (app/routes.php in Laravel 4), which will then decides what to do with the request.

Routes, Controllers and Views

If we look at the app/Http/routes.php file, you'll find

Route::get('/', 'WelcomeController@index');

Route::get('home', 'HomeController@index');

Route::controllers([
        'auth' => 'Auth\AuthController',
        'password' => 'Auth\PasswordController',
]);

So our / request is being passed to the index method of the WelcomeController controller. So let's take a look at that (app/Http/Controllers/WelcomeController.php)

<?php namespace App\Http\Controllers;

class WelcomeController extends Controller {
        public function __construct()
        {
                $this->middleware('guest');
        }
        public function index()
        {
                return view('welcome');
        }
}

Ignore the middleware part for now, and you'll see in the index() method, all it's doing is running the view() helper function.

The method returns a View instance for the path given as the argument. The default directory where Blade templates are kept is resources/views/, and so when we run view('welcome');, Laravel returns the view created at resources/views/welcome.blade.php.

<html>
  <head>
    <title>Laravel</title>
    ...
  </head>
  <body>
    <div class="container">
      <div class="content">
        <div class="title">Laravel 5</div>
        <div class="quote">{{ Inspiring::quote() }}</div>
      </div>
    </div>
  </body>
</html>

This HTML file is indeed what we saw on localhost:8000.

We had an overview of routes, controllers and views. We will focus more on controllers and views in subsequent posts, right now, we will dive deeper into routing.

routes.php

You can define different routing logic for different types of requests, such as GET, POST, PUT and DELETE.

Route::get();
Route::post();
Route::put();
Route::delete();
Route::any();

All of these methods take the path as its first parameter, and a function which returns something as its second parameter.

When the path matches that supplied, the function will run. For example, if we request (from our browser) http://domain.tld/path/to/awesome, it will match the following route, and in the browser you'd get the text Awesome indeed!.

Route::get('/path/to/awesome', function() {
    return 'Awesome indeed!';
});

You can use Route::any() for the route to match a path with any HTTP verb, or you can use Route::match() which lets you specify precisely which HTTP verbs this route applies to.

Route::match(['get', 'post'], '/path/to/awesome', function()
{
    return 'Awesome indeed!';
});

Route::any('/path/to/awesome', function() {
    return 'Awesome indeed!';
});

Route Parameters

You can capture values in the path and use it to alter what is returned.

Route::get('product/{id}', function($id)
{
    return 'Product '.$id;
});

In a more realistic example, let's say we have 5000 products on our e-commerce site, we're obviously not going to define a route for each one. Instead, we capture the product ID parameter in the path using curly brackets {}, and, through the function, pass it off to the controllers, which will consult the model, which in turn interacts with the database and returns the data. The data returned will depend on the captured product ID.

Route::get('/product/{pid}', function($pid)
{
    // Hand off to controller here
});
Optional Parameter

The route above is incomplete because what would happen if someone didn't provide a Product ID?

We must do one of the following:

  1. Define a route for /product

    Route::get('/product', function()
    {
        return 'List of all products';
    });
    
    
    Route::get('/product/{pid}', function($pid)
    {
        return 'Product #'.$pid
    });
    
  2. Make the paramter optional by appending a ? to the end of the parameter name, and write logic for when the parameter is null (you can use a ternary operator here, but I've expanded it for clarity).

    Route::get('/product/{pid?}', function($pid = null)
    {
        if ($pid === null) {
            return 'List of all products';
        } else {
            return 'Product #'.$pid
        }
    });
    
  3. Make the parameter optional (as before) and provide a default value

    Route::get('/product/{pid?}', function($pid = "0")
    {
        return 'Product #'.$pid
    });
    
Contraints

Constraints are where you constrain your route to only apply when your parameter meets certain conditions. For example, in the above example, we should ensure that pid consists only of digits and nothing else.

To achieve this, we use the where() method, which accepts the parameter name as the first argument, and a regular express which must be satisfied as the second argument.

Route::get('/product/{pid}', function($pid)
{
    // Hand off to controller here
})->where('pid' => '[0-9]+');

Now, it will only hand off to the controller when the pid parameters are digits-only.

You can specify multiple constraints by placing them in an array.

Route::get('user/{id}/{name}', function($id, $name)
{
    //
})
->where(['id' => '[0-9]+', 'name' => '[a-z]+'])

Named Routes

You can give your routes an alias.

Remember that instead of a closure, you can pass in an array as the second parameter of your Route::VERB() methods.

For naming routes, you specify the alias as the value for the as key in that array.

Route::get('path/to/my/deepest/thoughts', ['as' => 'deepthoughts', function()
{
    // Routing logic still goes here
}]);
Don't Repeat Yourself

You should always name your routes, because it keeps the DRY principle.

You can use the helper function route() to get you the URL based on the alias.

$url = route('routeName', $params);

You can use this helper function in your templates, your controllers,. And if you ever want to change the routes, you can change it once inside routes.php; without named routes, you'd have to change it everywhere you're using it.

Response object

Apart from returning a simple string back to the client, there are also other Response objects. View and Redirect are both a subclass of Response, for example.

You can create There are some responses so common that Laravel have created some useful shortcuts for; JSON and downloads are two good examples.

Custom Response

You can use the response helper method to respond with a custom Response object.

response($content, $status)
          ->header('Content-Type', $value);

Thus, you can make your own custom Response object by passing in your own content, status code and response headers.

However, some response objects are so common that Laravel has provided methods (called macros) specifically for them. You can create your own macros inside app/Providers/RouteServiceProvider.php.

View
response()->view('hello')->header('Content-Type', $type);
Redirects

Another Response object is the Redirect object, which you can instantiate and return using the redirect() helper method.

Route::get('home', function()
{
    return redirect('seasonal');
});

Route::get('seasonal', function()
{
    return 'Merry Christmas';
});

If you go to the home path, it will actually say Merry Christmas, because the redirect() method will use the Closure specified by the parameter.

Using this method will return with a code of 301

JSON

To return an array as a JSON object, just pass the array into response()->json() to convert it into a JSON string for the content. This will also set the appropriate Content-Type headers.

Downloads

If the response is supposed to be a file dowload, you can pass the path to the file into response()->download()

comments powered by Disqus