Dynamic Sidebars in Laravel 5 (an alternate approach)

Dynamic Sidebars in Laravel 5 (an alternate approach)

Posted Tuesday, May 26, 2015

I've been an avid Laravel developer for the past two or three years. It's clear, concise, well-supported and best of all, has made PHP development fun again.

Today I decided to share an easy technique I use in virtually every Laravel project I create—an alternate approach to creating dynamic sidebars.

The Use Case

Here's a common use case. Say you've got a blog. On every page of the blog (whether it's the homepage, a list of posts, a single post, etc...), you'd like to have a sidebar. And in that sidebar, you'd like a list of links to your most recently published posts.

Now, the DRY principle tells us to only code that sidebar once, even though it'll be on multiple pages, so, we'll call a partial view (or an include) for the sidebar. And, the SoC principle tells us to separate our view logic from our business logic.

So... where does the logic for retrieving your recently published posts go? Including the logic in the partial will be in violation of SoC, and including the logic in our controller actions will be in violation of DRY!

The Laravel Way: View Composers

The Laravel approach for this use case is the utilization of view composers. View composers are a nifty method of attaching business logic to one or more views. Check out the official docs for more information, but here's the gist of how they work.

The quick and very dirty method is to call View::composer() with a closure, passing along your recent posts as a variable:

View::composer('post._sidebar', function($view){
  $recentPosts = Post::published()->recent()->take(5)->get() ;
  $view->with('recentPosts', $recentPosts) ;
}) ;

Now, whenever the post/_sidebar.blade.php partial is called, it'll have access to a variable called $recentPosts.

Where you put the above code is entirely up to you. There really isn't a good place for it out of the box. The Laravel way is a lot more verbose than the above code (but also much less dirty!)—the view composer call should be wrapped up in a service provider. Again, check out the offical docs to see how this works.

An Alternate Approach

I'm not wild about view composers because the instantiation of that $recentPosts variable is just so far away from the controller actions! I like code that is as self-explanatory as possible. In the above example, glancing at your post controller and your sidebar view will give you no information about where $recentPosts is instantiated, and it will take quite a bit of digging to find that view composer.

In devising my own method of tackling this use case I harkened back to my days as a Symfony 1.x developer. Symfony had the concept of a component, which was like a mini controller action that would be called from within a view. Symfony 2 also has a handy method for calling a controller action from within a view—the render helper.

So let's add this feature to our Laravel 5 app.

The easiest way to do this would be creating a helper that we can call within our view. Let's create an app/Support directory and a new file called ViewHelpers.php inside it. Here we go:

/** app/Support/ViewHelpers.php **/

function include_action($controller, $action, $params = array())
{
  if (is_callable(array($controller, $action))) {
    $c = new $controller ;
    return $c->$action($params) ;
  }
}

Simple. Our include_action function accepts a controller class name, an action name that corresponds with the controller action we want to call, and an optional array of parameters.

Let's now make sure our ViewHelpers.php file is getting loaded by adding it to composer.json in the autoload section:

/** composer.json **/

  "autoload": {
    "classmap": [
      "database"
    ],
    "psr-4": {
      "App\\": "app/"
    },
    "files": [
      "app/Support/ViewHelpers.php"
    ]
  },

Let's create our controller action to retrieve and display the recent posts. Notice we're prefixing the method name with an underscore to differentiate it from our normal controller actions.

/** app/Http/Controllers/PostController.php **/

namespace App\Http\Controllers;

class PostController extends Controller
{
  /** ... */

  public function _recentPosts($parameters = array())
  {
    extract($parameters) ;
    $count = isset($count) ? $count : 10 ;
    $posts = Post::published()->recent()->orderBy('published_at', 'desc')->take($count)->get() ;
    return \View::make('post._recentPosts', ['posts'=>$posts]) ;
  }
}

Now all we have to do is create our posts/_recentPosts.blade.php view to display the list of posts and call our new _recentPosts action from within our sidebar view:

<!-- resources/views/post/_sidebar.blade.php -->

<aside class="sidebar">
  {!! include_action('App\Http\Controllers\PostController', '_recentPosts', ['count'=>5]) !!}
</aside>

That's all there is to it! This approach is compliant with both the DRY principle and the SoC principle, and the instantiation of our $recentPosts variable is in a very logical place.

Comments

Posted Monday, November 23, 2015, 8:54 am by Dylan
Normally I take laravel's opinions without thinking much but this is a great pattern if you want some quick reusable pieces while at the same time not wanting to create a JS component.
Posted Friday, June 5, 2015, 12:42 pm by Sam Ciaramilaro
Yes, thanks. I re-read after I posted the comment and found it. :)
Posted Sunday, May 31, 2015, 1:01 pm by Dave
Ah, read closer! ;) "include_action()" is a helper method I wrote (the code for it is in the blog post). It's not part of Laravel.
Posted Sunday, May 31, 2015, 7:41 am by Sam
Thanks. Nice solution. Where did you find documentation on the "include_action" method? I can't seem to find anything about that.

Add a Comment

Contact

Think I may be able to help you on your next project? Need some more information first? Let's connect! Please drop me a line via email or the form below, and I'll get back to you.