4
Php

Laravel-5.0 And Routing

Well, Laravel version 5.0 has some major changes in it’s folder structure and it’s really better than before. The new structure separates the Http Layer (The delivery mechanism of a web application) and inside the app directory there is a new Http folder which contains three folders, Controllers, Middleware, Requests and the routes.php file. As the title of this article tells that I’m going to discuss about Routing so it’s obvious that I’m not not discussing about anything but routes.php file here which used for route declarations.

The Laravel-5.0 is still in the development stage (at the time of writing of this post) and it may very at later releases, until the final release this post will be updated if required. So please stay tuned and check the final release of Laravel and the documentation on Laravel website as well.

Well, until Laravel version-5.0 we used to declare a route basically using something like this:

Route::get('url', 'ClassNameController@method');

It’s a very basic of routing and there are other ways tho, such as $app['router']->get(..) and even more ways and the routes.php file was in the app folder and it was just a normal PHP script but anyways.

In Laravel version 5.0 there are some changes in route declaration including some other things. First of all, the basic recommended way to declare a route in Laravel version 5.0 is something like this:

$router->get('url', 'ClassnameController@method');

Also, we may use something like this as well:

Route::get('url', 'ClassnameController@method');

Also using a helper method and a Closure, something like this:

get('url', function(){
    //...
});

In this case, if we use $router then it works and it means that, somehow the $router variable is available in the scope of routes.php file but it’s not a global variable in this case, instead, it’s a function scoped variable and that function is an anonymous function inside the App\Providers\RouteServiceProvider class but still available in the routes.php file and that is bacause the routes.php file is a part of the App\Providers\RouteServiceProvider class which is included within that class. Let’s check the code given below, which is taken from the App\Providers\RouteServiceProvider.php class:

public function map()
{
    $this->app->booted(function()
    {
        $this->namespaced('App\Http\Controllers', function(Router $router)
        {
            require app_path().'/Http/routes.php'; // <-- routes.php file is included here
        });
    });
}

In the map method, the innermost Closure the routes.php file is being included so the code in the routes.php file is actually a part of App\Providers\RouteServiceProvider class or we may think that, we are declaring the routes inside the class. Which means, if we use for example, dd($this) then we'll get a dumped App\Providers\RouteServiceProvider object. Since the App\Providers\RouteServiceProvider class extends the Illuminate\Foundation\Support\Providers\RouteServiceProvider class so it has access to it's parent class so that, it can call it's parent class' namespaced method and this namespaced method is as given below:

protected function namespaced($namespace, Closure $callback)
{
    if (empty($namespace))
    {
        $callback($this->app['router']);
    }
    else
    {
        $this->app['router']->group(compact('namespace'), $callback);
    }
}

So, it's now obvious that, the namespaced method calls the Closure (with the only argument $this->app['router']) that is passed as the second argument of namespaced method from the map method of App\Providers\RouteServiceProvider class and the map method actually registers the $app->booted event handler (The Closure passed into the booted(...) is the handler) so when the booted event gets fired by the framework, the namespaced method gets called and so the app/Http/routes.php file gets included inside the App\Providers\RouteServiceProvider class and the real magic actually happens inside the the Illuminate\Foundation\Support\Providers class' boot method; from where the process begins. The boot method looks something like this:

public function boot()
{
    // Calls the before method of App\Providers\RouteServiceProvider, 
    // route model binding should be coded here in the "before" method
    $this->app->call([$this, 'before']);

    // If routes are cached (new feature in version-5.0) then load cached routes
    if ($this->app->routesAreCached()) return $this->loadCachedRoutes();

    // Otherwise load routes.php
    $this->loadRoutes();
}

The loadRoutes method looks something like this:

protected function loadRoutes()
{
    // Loads scanned (annotated) routes (new feature in version 5.0)
    if ($this->app->routesAreScanned()) $this->loadScannedRoutes();

    // Calls the "map" method from child class
    // to register the booted event handler
    $this->app->call([$this, 'map']);
}

So, that's pretty obvious that, when the frameworks gets booted then the routes.php file gets included through the RouteServiceProvider class and routes.php file is actually the App\Providers\RouteServiceProvider class itself, at least we can think so. As a result, the $router variable is scoped to that Closure and also the route declarations as well, check that anonymous function again here:

$this->namespaced('App\Http\Controllers', function(Router $router)
{
    // $routes is available here and scope is limited to
    // this Closure, separated from global scope
    require app_path().'/Http/routes.php';
});

The "namespaced" method also loads the routes file within a route group which automatically sets the App\Http\Controllers namespace as well. If the first argument is passed (which is App\Http\Controllers) then the following line (in namespaced method) does the trick:

$this->app['router']->group(compact('namespace'), $callback);

It sets the namespace so we don't need to use the namespace in route declaration manually, this is really awesome.

Now, lets check the before method in App\Providers\RouteServiceProvider class. Well, this function is useful for route model binding, which means if we want to bind models to our routes then we need to do it within this before method but in the earlier versions we used to do it in the routes.php file, so let's see how can we do it, it's just as simple as it was before:

public function before(Router $router, UrlGenerator $url)
{
    $url->setRootControllerNamespace('App\Http\Controllers');

    // Binding the User model to 'example.com/user/1' route
    // so the user model will be injected automatically
    $router->model('user', 'App\User');
}

Also, we can use something like this as well:

public function before(Router $router, UrlGenerator $url)
{
    $url->setRootControllerNamespace('App\Http\Controllers');

    // Binding the User model to 'example.com/user/heera' route
    // so the user model will be injected automatically
    $router->bind('user', function($name){ // $name = heera
        return \App\User::where('username', $name)->first();
    });
}

Read more about routing on Laravel website. The before method gets called before our routes are loaded. So, this is different from older versions of the framework. Also, in version 5.0 we are able to use route annotations, which means if we don't want to declare our routes in the routes.php file manually then we may leave it to the framework but in this case we need to use annotations in the controller, for example we may declare a method in a class with annotations, using something like this:

/**
 * @Get("/users/{id}", as="user.show")
 */
public function show($id)
{
    // ...
}

Now, if we run artisan route:scan from command prompt/terminal then it'll automatically generate the routes for us and that will be saved in the storage/framework/routes.scanned.php file so we don't need to declare the route manually in the routes.php file.

We can also use annotations for Middleware and Event as well, for example:

/**
 * @post("/users/create", as="user.create")
 * @Middleware("auth")
 */
public function register($id)
{
    // ...
}

For event we may use something like this:

/**
 * @Hears("user.signup")
 */
public function UserRegistration(User $user)
{
    // ...
}

In this case you need to run artisan event:scan from the command prompt/terminal so the framework will register the listener or listeners (if there are more) into storage/framework/events.scanned.php file.

While annotation support is great but I like be explicit on route declaration and it looks properly organized to me but anyways, it's just personal preference. You may also like this, it's about Middleware. Have a good time 🙂

Latest Blog

0
Php

PHP – 8.0 Match Expression

In PHP 8.0 there is a new feature or I should say a new language construct (keyword) going to be introduced,  which has been implemented depending […]