Laravel 5.1 Beauty - Starting the Admin Area

Creating the skeleton of the administration area for our blog.

Posted on April 5, 2015 in L5 Beauty
Note: this is the sixth step in the tutorial.

In this chapter we’ll continue building on the l5beauty project and start developing the administration area.

Chapter Contents

Establishing the Routes

In the last chapter, when creating the blog in 10 minutes, the app/Http/routes.php file was set up for the blog. Now, we’ll add some additional routes for administration.

Why Routes?

Laravel 5.1 needs a way to tie web requests to the code that handles the web request. This is called routing. All routing within this book is defined in the app/Http/routes.php file.

Whenever a web request is made to a file that doesn’t exist within the public directory, Laravel 5.1 looks at the routes file to determine what to return. For instance, a web request to /css/app.css will be handled by the web server (assuming public/css/app.css exists), but a request to /blog/my-welcome-page doesn’t exist in the public directory and Laravel 5.1 tries to find a matching route to execute.

Update the app/Http/routes.php to match what’s below.

Adding Admin Routes

<?php

// Blog pages
get('/', function () {
  return redirect('/blog');
});
get('blog', 'BlogController@index');
get('blog/{slug}', 'BlogController@showPost');

// Admin area
get('admin', function () {
  return redirect('/admin/post');
});
$router->group([
  'namespace' => 'Admin',
  'middleware' => 'auth',
], function () {
  resource('admin/post', 'PostController');
  resource('admin/tag', 'TagController');
  get('admin/upload', 'UploadController@index');
});

// Logging in and out
get('/auth/login', 'Auth\AuthController@getLogin');
post('/auth/login', 'Auth\AuthController@postLogin');
get('/auth/logout', 'Auth\AuthController@getLogout');

Line 11 - 13
Redirect requests to /admin to the /admin/post page
Line 14 - 16
Start a routing group using the namespace Admin (which actually will expand out to App\Http\Controllers\Admin) and force the auth middleware to be active. (See the section below on Middleware).
Line 18 - 19
Within the route group, add two Resource Controllers (see below).
Line 20
Add a route so whenever a GET request is made to /admin/upload the index() method of App\Http\Controllers\Admin\UploadController will be called.
Line 24 - 26
Here we add routes for logging in and logging out.

Once you save the routes.php file, the next step will be to create any missing controllers. But before we get to that, let’s explore a couple concepts: Middleware and Resource Controllers.

Tip - Read the Docs

Check out the Laravel 5.1 documentation on routing for more information about routing.

Middleware

If you used Laravel 4 you may recall the concept of filters. The middleware in Laravel 5.1 provides the functionality that filters did in Laravel 4, but they are more aptly named.

Abstract Flow of Request to Response

HTTP request received
-> Check for maintenance mode*
   -> Start session*
      -> Get response from controller action
   -> Encrypt Cookies*
-> Add cookies to response*
Return response to user

In this simplified flow from a request to the response, the items with the asterisk(*) at the end are considered middleware.

The file app/Http/Kernel.php contains a list of the middleware for your application. When viewing this file note the $middleware property contains the global middleware (that is, middleware always executed) and the $routeMiddleware property contains a list of middleware that can be applied at the route level

Auth Middleware

Let’s say a route has the auth middleware active, the flow would look similar to what’s below.

Request to Response with auth middleware

HTTP request received
-> Global "before" middleware
   -> (auth) If not logged on then return redirect to logon form
      -> Get response from controller action
-> Global "after" middleware
Return response to user

Thus, if the auth middleware detects the user is not logged on, the controller action is never executed and, instead, the user is redirected to the logon page.

More About Middleware

The best place to learn more about Laravel 5.1 Middleware is the official Laravel 5.1 documentation.

Resource Controllers

In our routing file earlier we specified a resourceful route using the resource() function. This single declaration creates multiple routes, names those routes, and points them to a series of expected action methods on the controller.

For example, the resource('admin/post', 'PostController') statement sets up all the routes in the table below.

Table 8.1 - Resource routes on the Post controller

HTTP Verb Path Action Method Route Name
GET admin/post index() admin.post.index
GET admin/post/create create() admin.post.create
POST admin/post store() admin.post.store
GET admin/post/{post} show() admin.post.show
GET admin/post/{post}/edit edit() admin.post.edit
PUT/PATCH admin/post/{post} update() admin.post.update
DELETE admin/post/{post} destroy() admin.post.destroy

Creating the Admin Controllers

Now that the routes are set up for the administration area, use artisan to create the controllers.

Creating the admin controllers with artisan

~/Code/l5beauty$ php artisan make:controller Admin\\PostController
Controller created successfully.

~/Code/l5beauty$ php artisan make:controller Admin\\TagController
Controller created successfully.

~/Code/l5beauty$ php artisan make:controller Admin\\UploadController --plain
Controller created successfully.

Upon completion of the above three artisan commands, there will be three new controller files in the app/Http/Controllers/Admin directory.

NOTE: The --plain option was only used on the upload controller. The PostController.php and TagController.php files will be created with all the required methods stubbed out.

Try the artisan route:list command

If you use the artisan route:list command now from the Homestead VM you’ll see all the routes and all the actions and controllers they map to.

Update the index() method within the PostController class to match what’s below.

PostController’s index() method

  /**
   * Display a listing of the posts.
   *
   * @return Response
   */
  public function index()
  {
    return view('admin.post.index');
  }

The index() method simply returns the view. We’ll build it shortly.

Creating the Views

There’s a few views we need to create. Let’s just run through them one-by-one.

Creating an Admin Layout

The Blade templating engine is one of the most powerful features of Laravel. We’ll set up a layout to use for our blog administration which will give the administrationi area a consistent look.

Create a directory named resources/views/admin and within this directory create a layout.blade.php file with the following content.

Content of admin.layout view

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>{{ config('blog.title') }} Admin</title>

  <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"
        rel="stylesheet">
  @yield('styles')

  <!--[if lt IE 9]>
    <script src="//oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
    <script src="//oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
  <![endif]-->
</head>
<body>

{{-- Navigation Bar --}}
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed"
              data-toggle="collapse" data-target="#navbar-menu">
        <span class="sr-only">Toggle Navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">{{ config('blog.title') }} Admin</a>
    </div>
    <div class="collapse navbar-collapse" id="navbar-menu">
      @include('admin.partials.navbar')
    </div>
  </div>
</nav>

@yield('content')

<script
src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script
src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>

@yield('scripts')

</body>
</html>

This snippet of code may look familiar. It’s the Basic template for Bootstrap. There’s just a few additional hooks in it.

<title>{{ config('blog.title') }} Admin</title>
Here we set the title of the page to the blog’s title with the word “Admin” added on.
@yield('styles')
This Blade directive will output the section (if there is one) named styles. It allows us to put some extra CSS at the top of the template.
@include('admin.partials.navbar')
Here we’re including another blade template (which does not yet exist).
@yield('content')
This will output the main content of the page.
@yield('scripts')
Here’s where additional javascript can be output.

Escaped or Unescaped?

Blade templates provide two ways to output PHP expressions. You can wrap the PHP code in double curly braces {{ 'like this' }} and the value of the PHP expression will be output at that point in the template, but the output will be escaped—meaning HTML entities will be encoded. If you want the value to not be escaped, then wrap the expression in a curly brace and double exclamation {!! 'like this' !!}.

Creating the Navbar Partial

This view is the one the layout includes.

Create the new directory resources/admin/partials and within that directory create a navbar.blade.php file with the following content.

Initial Content of admin.partials.navbar view

<ul class="nav navbar-nav">
  <li><a href="/">Blog Home</a></li>
  @if (Auth::check())
    <li @if (Request::is('admin/post*')) class="active" @endif>
      <a href="/admin/post">Posts</a>
    </li>
    <li @if (Request::is('admin/tag*')) class="active" @endif>
      <a href="/admin/tag">Tags</a>
    </li>
    <li @if (Request::is('admin/upload*')) class="active" @endif>
      <a href="/admin/upload">Uploads</a>
    </li>
  @endif
</ul>

<ul class="nav navbar-nav navbar-right">
  @if (Auth::guest())
    <li><a href="/auth/login">Login</a></li>
  @else
    <li class="dropdown">
      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
         aria-expanded="false">{{ Auth::user()->name }}
        <span class="caret"></span>
      </a>
      <ul class="dropdown-menu" role="menu">
        <li><a href="/auth/logout">Logout</a></li>
      </ul>
    </li>
  @endif
</ul>

If a user is logged in, then this template displays a menu for Posts, Tags, and Uploads on the left and a Logout on the right.

If there is no user logged in then only a Login link is displayed on the right.

Creating the Login Form

Now that we have an admin layout, creating a login form is a bit simpler. Create the resources/views/auth directory and within this directory create a login.blade.php file with the following content.

Content of auth.login view

@extends('admin.layout')

@section('content')
  <div class="container-fluid">
    <div class="row">
      <div class="col-md-8 col-md-offset-2">
        <div class="panel panel-default">
          <div class="panel-heading">Login</div>
          <div class="panel-body">

            @include('admin.partials.errors')

            <form class="form-horizontal" role="form" method="POST"
                  action="{{ url('/auth/login') }}">
              <input type="hidden" name="_token" value="{{ csrf_token() }}">

              <div class="form-group">
                <label class="col-md-4 control-label">E-Mail Address</label>
                <div class="col-md-6">
                  <input type="email" class="form-control" name="email"
                         value="{{ old('email') }}" autofocus>
                </div>
              </div>

              <div class="form-group">
                <label class="col-md-4 control-label">Password</label>
                <div class="col-md-6">
                  <input type="password" class="form-control" name="password">
                </div>
              </div>

              <div class="form-group">
                <div class="col-md-6 col-md-offset-4">
                  <div class="checkbox">
                    <label>
                      <input type="checkbox" name="remember"> Remember Me
                    </label>
                  </div>
                </div>
              </div>

              <div class="form-group">
                <div class="col-md-6 col-md-offset-4">
                  <button type="submit" class="btn btn-primary">Login</button>
                </div>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
@endsection

Just a couple things to note in with this form.

We included a not-yet-created admin.partials.errors. This will be created next.

The old() function used to output the value of the email field will contain the value entered if this form generates an error and is displayed again.

Creating the Errors Partial

Checking for input errors and displaying those errors is such a common task when dealing with forms that we’re breaking it out into its own tiny Blade template.

Create a new errors.blade.php file in the resources/views/admin/partials directory with the following content.

Content of admin.partials.errors view

@if (count($errors) > 0)
  <div class="alert alert-danger">
    <strong>Whoops!</strong>
    There were some problems with your input.<br><br>
    <ul>
      @foreach ($errors->all() as $error)
        <li>{{ $error }}</li>
      @endforeach
    </ul>
  </div>
@endif

The $errors variable is available to every view. It will contain a collection of errors, if there are any. So here we just check if there’s any errors and output them.

Creating the Post Index View

Create a new directory named resources/views/admin/post and within that directory a new file named index.blade.php with the following content.

Initial Content of admin.post.index view

@extends('admin.layout')

@section('content')
  <div class="container-fluid">
    <div class="row">
      <div class="col-md-8 col-md-offset-2">
        <div class="panel panel-default">
          <div class="panel-heading">
            <h3 class="panel-title">Posts</h3>
          </div>
          <div class="panel-body">

            TODO

          </div>
        </div>
      </div>
    </div>
  </div>
@stop

This is just a temporary view at this point. In a future chapter we’ll finish it.

Testing logging in and out

Point your browser to http://l5beauty.app/admin and see what happens. You should see the logon screen.

Here’s actually what happened.

  1. The ‘admin’ route was matched (in app/Http/routes.php) and the closure was executed which redirected your browser to ‘/admin/post’.
  2. The route named ‘admin.post.index’ (see artisan route:list) was matched from the ‘/admin/post’ URI, but the auth middleware determined no user was logged in and did another redirect, this time to ‘/auth/login’.
  3. The ‘/auth/login’ route found a match and executed the getLogin() method of the Auth\AuthController. (This method actually resides in the AuthenticateUsers trait.)
  4. The getLogin() method returned the contents of the ‘auth.login’ view, which is the login screen you should have seen.

Now, you could try logging in, but at this point it won’t do much good since we haven’t added a user to the system yet.

Creating the admin user

The artisan tinker command is a handy way to interact with your application. Follow the steps below to create an administration user for the l5beauty blog.

Creating a user using tinker

~/Code/l5beauty$ php artisan tinker
Psy Shell v0.4.3 (PHP 5.6.7-1+deb.sury.org~utopic+1 — cli) by Justin Hileman
>>> $user = new App\User;
=> <App\User #000000007543b78f0000000009f4a1ca> {}
>>> $user->name = 'Your Name';
=> "Your Name"
>>> $user->email = 'YOUR@email.com';
=> "YOUR@email.com"
>>> $user->password = bcrypt('YOUR PASSWORD');
=> "$2y$10$gBF9EIr9IrIbMX7dwQsCTO6IsBC0/c0P6qzZ01zwPhoW61MMwOVgC"
>>> $user->save();
=> true
>>> exit;

Now you’ll be able to log in with this user you just created. Go back to the login page on the browser and give it a try.

A successful login should present you with the page below.

Figure 8.1 - List of Posts

List of Posts

Fixing log out location

If you click on the pull down at the top right of the screen and choose Logout to log out, you’ll notice that instead of the login page, now you’re back at the blog page.

Why is this?

If you look at the AuthController class (in the app/Http/Controllers/Auth directory) for the getLogout() method, you’ll discover there isn’t any method by that name. And there’s no getLogout() in the parent class, nor the grandparent. But AuthController does use the AuthenticatesAndRegistersUsers trait, and that uses the AuthenticatesUsers trait. It’s in the AuthenticateUsers trait where the getLogout() method is hiding.

So if you look way down the directory path in the vendor/laravel/framework/src and go further down into the Illuminate/Foundation/Auth directory you’ll find this trait in the AuthenticatesUsers.php file. And in that file, you can see the getLogout() method redirects to the root path (‘/’) when done.

To fix this, edit the AuthController class (it’s in the app/Http/Controllers/Auth directory) so it looks like what’s below.

Content of AuthController class

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class AuthController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectAfterLogout = '/auth/login';
    protected $redirectTo = '/admin/post';

    /**
     * Create a new authentication controller instance.
     */
    public function __construct()
    {
        $this->middleware('guest', ['except' => 'getLogout']);
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|confirmed|min:6',
        ]);
    }
}

We removed the AuthenticateAndRegistersUsers trait because our application isn’t going to allow users to be registered, instead we’re just using the AuthenticatesUsers trait. Then we added the $redirectAfterLogout and $redirectTo properties to specify where to redirect after logins and logouts.

The reset of the class is the same as it was before.

Fix default login location

The default login location is currently set to /home, but we’ll change it.

Edit the RedirectIfAuthenticated.php file located in the app/Http/Middleware directory. Change line #38 to what is below.

Change default logged in location

// Line #38 should be
return new RedirectResponse('/home');

// Change it to
return new RedirectResponse('/admin/post');

This path will redirect when the guest middleware is used on a route.

Logging in and out

Since the getLogout() method is now fixed to return back to the administration area instead of the blog, you should be able to log in and out successfully.

Try it.

  • Point your browser back to http://l5beauty.app/admin. This will redirect you to the log in page.
  • Enter the credentials for the user you created. This will take you to the List of Posts page.
  • Use the dropdown in the navbar to logout. This will take you back to the log in page.

Cleaning up a couple unneeded files

There’s a couple files we don’t need. Let’s remove them.

  • app/Http/Controllers/Auth/PasswordController.php - We’re not going to implement password resets so this isn’t needed.
  • resources/views/welcome.blade.php - Not using.

Recap

Quite a bit was accomplished in this chapter. The routes were established for most of the administration area and there was a brief discussion about Middleware and Resource Controllers in Laravel 5. The logging in and out process was customized specifically for our administration area.

All in all, we now have a strong base to build the admin area upon. In the next chapter we’ll start making the admin area useful and add Tags to our blog system.

comments powered by Disqus