Creating a Blog Using Laravel 4 Part 2: Controllers

Creating a Blog Using Laravel 4 Part 2

Laravel 4 Blog Tutorial – dashboard Screenshot

In the first part of Creating Blog Using Laravel 4 we had covered: Laravel setup, Laravel Migrations, model creation using Eloquent ORM and Database Seeding. In this part of the tutorial we will cover the controllers for our blog application.

Creating Controllers

In Laravel we create controllers by extending BaseController class present inside app/controllers directory. All of our controller classes reside inside app/controllers directory.

Note:
Laravel does not have any restrictions on the directory structure of the application, we can rearrange the application’s directory structure according to our needs and adjust the composer settings accordingly.

An example controller:

<?php
//file: app/controllers/IndexController.php

class IndexController extends BaseController {

    public function showIndex() 
    {
    	// generates response from index.blade.php
        return View::make('index');
    }
}

//file: app/routes.php
//registering route to controller actions

Route::get('index','IndexController@showIndex');

//In general
Route::get('route.name','SomeController@someAction');
Route::post('route.name','SomeController@someAction');

So an action inside controller has a corresponding route entry in app/routes.php. We can also create RESTful controllers by prefixing controller’s action name with HTTP verb it responds to like this:

<?php

class IndexController extends BaseController {

    public function getAction() 
    {
        //get request handling
    }
    public function postAction()
    {
    	//post request handling
    }
}

//registering route
Route::controller('index','IndexController);

To register routes to a RESTful controller’s actions we use Route::controller('route.name','ControllerName') method which automatically registers all the routes to REST actions.

Notes:

  • Normally an action has an associated view. This view serves the purpose of an actions’s intended response.
  • When we return an array from an action, Laravel automatically converts this array to JSON encoded response.
  • We use protected $layout property to set a master view for a controller.
  • We can nest partial views inside master view using nest() method.
  • We can pass data to the views using with() method or by directly assigning the variable using a dynamic property like: $this->layout->variable='value'.

Controllers for Blog Application

We will start by creating the BlogController which will handle the requests like: serving the home / index page of the blog, querying and displaying search results, user login and logout.

To handle CRUD operations on posts and comments we will create a PostsController and a CommentsController respectively.

The BlogController

Here is the code for app/controllers/BlogController.php:

<?php
//file: app/controllers/BlogController.php

class BlogController extends BaseController {

    public function __construct()
    {
        //updated: prevents re-login.
        $this->beforeFilter('guest',['only' => ['getLogin']]);
        $this->beforeFilter('auth',['only' => ['getLogout']]);
    }
    public function getIndex() 
    {
        $posts = Post::orderBy('id','desc')->paginate(10);
        // For Laravel 4.2 use getFactory() instead of getEnvironment() method.
        $posts->getEnvironment()->setViewName('pagination::simple');
        $this->layout->title = 'Home Page | Laravel 4 Blog';
        $this->layout->main = View::make('home')->nest('content','index',compact('posts'));
    }

    public function getSearch()
    {
        $searchTerm = Input::get('s');
        $posts = Post::whereRaw('match(title,content) against(? in boolean mode)',[$searchTerm])
                     ->paginate(10);
        // For Laravel 4.2 use getFactory() instead of getEnvironment() method.
        $posts->getEnvironment()->setViewName('pagination::slider');
        $posts->appends(['s'=>$searchTerm]);
        $this->layout->with('title','Search: '.$searchTerm);
        $this->layout->main = View::make('home')
                     ->nest('content','index',($posts->isEmpty()) ? ['notFound' => true ] : compact('posts'));
    }

    public function getLogin() 
    {
        $this->layout->title='login';
        $this->layout->main = View::make('login');
    }

    public function postLogin()
    {
        $credentials = [
            'username'=>Input::get('username'),
            'password'=>Input::get('password')
        ];
        $rules = [
            'username' => 'required',
            'password'=>'required'
        ];
        $validator = Validator::make($credentials,$rules);
        if($validator->passes())
        {
            if(Auth::attempt($credentials))
                return Redirect::to('admin/dash-board');
            return Redirect::back()->withInput()->with('failure','username or password is invalid!');
        }
        else
        {
            return Redirect::back()->withErrors($validator)->withInput();
        }
    }

    public function getLogout()
    {
        Auth::logout();
        return Redirect::to('/');
    }

}

When we will register routes with Route::controller() ( i.e, Route::controller('/','BlogController')), the getIndex() action will be mapped to the default route of the application.

Inside getIndex() action we are querying the Post model to get a Paginator instance with 10 posts per page using the paginate() method. This instance is then passed to the index view of the application. We will loop through this variable inside our index view to display an index of the posts with the pagination links at the bottom of the page.

We will use the getSearch() action to provide the search functionality on the blog. In this action we are querying the Post model for the search terms. The whereRaw() method is used to specify a MySQL fulltext search criteria, the matching results are then retrieved using the paginate() method to enable the pagination on the resulting models. Also, we need to append the search query to the pagination links. Otherwise, pagination on search results will not work. This is done using the $post->appends() method call.

The remaining three actions will be used in user authentication. See my article on Laravel Authentication for explanation.

The PostsController

The code for PostController:

<?php
//file: app/controllers/PostsController.php

class PostController extends BaseController
{

    /* get functions */
    public function listPost()
    {
        $posts = Post::orderBy('id','desc')->paginate(10);
        $this->layout->title = 'Post listings';
        $this->layout->main = View::make('dash')->nest('content','posts.list',compact('posts'));
    }
    public function showPost(Post $post)
    {
        $comments = $post->comments()->where('approved', '=', 1)->get();
        $this->layout->title = $post->title;
        $this->layout->main = View::make('home')->nest('content', 'posts.single', compact('post', 'comments'));
    }

    public function newPost()
    {
        $this->layout->title = 'New Post';
        $this->layout->main = View::make('dash')->nest('content', 'posts.new');
    }

    public function editPost(Post $post)
    {
        $this->layout->title = 'Edit Post';
        $this->layout->main = View::make('dash')->nest('content', 'posts.edit', compact('post'));
    }

    public function deletePost(Post $post)
    {
        $post->delete();
        return Redirect::route('post.list')->with('success', 'Post is deleted!');
    }

    /* post functions */
    public function savePost()
    {
        $post = [
            'title' => Input::get('title'),
            'content' => Input::get('content'),
        ];
        $rules = [
            'title' => 'required',
            'content' => 'required',
        ];
        $valid = Validator::make($post, $rules);
        if ($valid->passes())
        {
            $post = new Post($post);
            $post->comment_count = 0;
            $post->read_more = (strlen($post->content) > 120) ? substr($post->content, 0, 120) : $post->content;
            $post->save();
            return Redirect::to('admin/dash-board')->with('success', 'Post is saved!');
        }
        else
            return Redirect::back()->withErrors($valid)->withInput();
    }

    public function updatePost(Post $post)
    {
        $data = [
            'title' => Input::get('title'),
            'content' => Input::get('content'),
        ];
        $rules = [
            'title' => 'required',
            'content' => 'required',
        ];
        $valid = Validator::make($data, $rules);
        if ($valid->passes())
        {
            $post->title = $data['title'];
            $post->content = $data['content'];
            $post->read_more = (strlen($post->content) > 120) ? substr($post->content, 0, 120) : $post->content;
            if(count($post->getDirty()) > 0) /* avoiding resubmission of same content */
            {
                $post->save();
                return Redirect::back()->with('success', 'Post is updated!');
            }
            else
                return Redirect::back()->with('success','Nothing to update!');
        }
        else
            return Redirect::back()->withErrors($valid)->withInput();
    }

}

Route Model Bindings
Laravel has a nice little feature for binding models to the route parameters. We use Route::model('parameter.name','model.name') to specify the bindings.

For instance:

<?php
//file: app/routes.php

Route::model('post','Post');

Route::get('post/{post}',function(Post $post)
{
    echo $post->title;

});

Now when we will visit this route with a Post id as a parameter, A Post model corresponding to this id will be automatically injected to the route handler closure / controller action.

The only public action in PostController is showPost which will be used to display a single post on the blog along with its comments. Inside showPost action, we are retrieving all the approved comments for the current post, the post model and comments are then passed to the view named as ‘single’ (app/views/posts/single.blade.php).

The remaining actions in this controller will be used for performing CRUD actions on the Post model. We will use the listPost action inside administrator dashboard to display all the posts in a tabular form with links to CRUD.

The newPost action will be used to display a form through which the admin user will be able to post new content on the blog. The form submission will be handled by savePost action. In savePost, we will save the request data if it passes validation rules otherwise we will redirect back with validation errors.

The CommentsController

The code for CommentsController:

<?php
//file: app/controllers/CommentsController.php

class CommentController extends BaseController {

    /* get functions */
    public function listComment()
    {
        $comments = Comment::orderBy('id','desc')->paginate(20);
        $this->layout->title = 'Comment Listings';
        $this->layout->main = View::make('dash')->nest('content','comments.list',compact('comments'));
    }

    public function newComment(Post $post)
    {
        $comment = [
            'commenter' => Input::get('commenter'),
            'email' => Input::get('email'),
            'comment' => Input::get('comment'),
        ];
        $rules = [
            'commenter' => 'required',
            'email' => 'required | email',
            'comment' => 'required',
        ];
        $valid = Validator::make($comment, $rules);
        if($valid->passes())
        {
            $comment = new Comment($comment);
            $comment->approved = 'no';
            $post->comments()->save($comment);
            /* redirect back to the form portion of the page */
            return Redirect::to(URL::previous().'#reply')
                    ->with('success','Comment has been submitted and waiting for approval!');
        }
        else
        {
            return Redirect::to(URL::previous().'#reply')->withErrors($valid)->withInput();
        }
    }

    public function showComment(Comment $comment)
    {
        if(Request::ajax())
            return View::make('comments.show',compact('comment'));
        // handle non-ajax calls here
        //else{}
    }

    public function deleteComment(Comment $comment)
    {
        $post = $comment->post;
        $status = $comment->approved;
        $comment->delete();
        ($status === 'yes') ? $post->decrement('comment_count') : '';
        return Redirect::back()->with('success','Comment deleted!');
    }

    /* post functions */

    public function updateComment(Comment $comment)
    {
        $comment->approved = Input::get('status');
        $comment->save();
        $comment->post->comment_count = Comment::where('post_id','=',$comment->post->id)
            ->where('approved','=',1)->count();
        $comment->post->save();
        return Redirect::back()->with('success','Comment '. (($comment->approved === 'yes') ? 'Approved' : 'Disapproved'));
    }

}

In the code above, listComment action will be used for displaying a list of comments along with CRUD links in the admin dashboard. When a new comment will be posted on the blog, newComment action will be used to validate and save the comment in the database. In the newComment action, we will save the comments with approved field set to ‘no’ by default. This will give the admin an opportunity to approve or disapprove a comment using updateComment action. In updateComment action we are also updating the comment_count field of the Post model based on the approved comments. The deleteComment action will delete a comment from the database.

The showComment simply returns a partial view, we will utilize this partial view for a quick view of the posted comment using Ajax in admin dashboard. We will cover this in the ‘views and layout’ part of this tutorial.

In the next part of this tutorial we will cover the routing for this application. Regards!


6 thoughts on “Creating a Blog Using Laravel 4 Part 2: Controllers

  1. Zaheen Reply

    This is the simplest and the best article I have read about Laravel for beginners.
    Thank you

  2. Zaheen Reply

    Hi Usman,
    Is you code perfectly fit for Laravel 4.1.26? Because I have read an article which says something about ‘remember_token’.

    Will this article is fine with those changes?
    Thank you

    1. Usman Riaz author Reply

      Yes, the code is patched to work with Laravel 4.1.26. You can download the patched version of the code from here.

  3. Zaheen Reply

    Thank you very much for the reply and can you please do a tutorial like this for a product listing site with a content management system, I would really like to see how image upload and image display with resize in laravel.

    I hope you will do this type of lesson for please.. please

    Thank you

    Zaheen

  4. kyo Reply

    method getEnvironment didn’t isset in laravel 4.2

    1. Usman Riaz author Reply

      Thanks for the update. It has been changed to getFactory, I have updated the code comments in the article.

Leave a Reply

Your email address will not be published. Required fields are marked *


seven + = 8

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>