Code Management in Drupal 7 using Features, Ctools, and Panels
Code structure is something most Drupal developers wrestle with. There are tons of modules out there that make our lives easier (Views, Display Suite, etc.) but managing database configuration while maintaining a good workflow is no easy challenge. Today I'm going to talk about a few approaches I use in my work here at Echo. We will be using a simple use case of creating a paginated list of blog posts. To start, we're going to talk about the workflow from a high level, then we'll get into the modules that leverage Drupal in a way that makes sense. Finally, we'll have some code samples to help guide things along.
Workflow
This will vary a bit based on what you need, but the idea behind this is we never want to redo our work. Ideally we'd like to design a View or functionality once on our local, and then package it and push it up. Features is a big driving force behind this. Beyond that, we want things like page structures and custom code to have a place to live that makes sense. So, for this example we will be considering the idea of a paginated list of Blog Posts. This is a heavy hammer to be swinging at such a solved task, but we will get into why this is good later on.
- Create a new Feature that requires ctools and panels (and not views!)
- Open up the generated .module file and declare the ctool plugin directory
- Create the plugins/content_types/blog_posts.inc file
- Define the needed functions within blog_posts.inc to make it work
- Add the newly created content type to a page in Page Manager
- Add everything we care about to the Feature and export it for deployment
Installation
This only assumes that you have a working Drupal installation and some knowledge of how to install modules. In this case, we will be using drush to accomplish this, but feel free to pick your poison here. Simply run the following commands and answer yes when prompted.
drush dl ctools ds features panels strongarm
drush en ctools ds features panels strongarm page_manager
What we have done here is install and enable a strong foundation on which we can start to scaffold our site. Note that I won't be getting into folder structure too much, but there are some more steps before this you would have to take to ensure contrib, custom, and features all make it to their own place. We wave our hands at this for now.
Features
The first thing we're going to do is generate ourselves a Feature. Simply navigate to Structures -> Features -> Create Feature and you will see a screen that looks very similar to this. Fill out a name, and have it require ctools and panels for now.
This will generate a mostly empty feature for us. The important part we want here is the ability to turn it on and off in the Features UI, and the structure (that we didn't have to create manually!) which includes a .module and .info file is ready to go for us. That being said, we're going to open it up and tell it where to find the plugins. The code to do that is below, and here is a screenshot of the directory structure and code to make sure you're on the right track. Go ahead and create the plugins directory and associated file as well.
function blog_posts_ctools_plugin_directory($owner, $plugin_type) {
return 'plugins/' . $plugin_type;
}
Chaos Tools
Known more commonly as ctools, this is a module that allows us this plugin structure. For our purposes, we've already made the directory and file structure needed. Now all we have to do is create ourselves a plugin. There are three key parts to this: plugin definition, render function, and form function. These are all defined in the .inc file mentioned above. There are plenty of resources online that get into the details, but basically we're going to define everything that gets rendered in code and leverage things like Display Suite and the theme function for pagination. This is what we wind up with:
<?php
/**
* Plugin definition
*/
$plugin = array(
'single' => TRUE,
'title' => t('Blog Post Listing'),
'description' => t('Custom blog listing.'),
'category' => t('Custom Views'),
'edit form' => 'blog_post_listing_edit_form',
'render callback' => 'blog_post_listing_render',
'all contexts' => TRUE,
);
/**
* Render function for blog listing
* @author Austin DeVinney
*/
function blog_post_listing_render($subtype, $conf, $args, &$context) {
//Define the content, which is built throughout the function
$content = '';
//Query for blog posts
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node', '=')
->entityCondition('bundle', 'blog_post', '=')
->propertyCondition('status', NODE_PUBLISHED, '=')
->pager(5);
//Fetch results, and load all nodes
$result = $query->execute();
//If we have results, build the view
if(!empty($result)) {
//Build the list of nodes
$nodes = node_load_multiple(array_keys($result['node']));
foreach($nodes as $node) {
$view = node_view($node, 'teaser');
$content .= drupal_render($view);
}
//Add the pager
$content .= theme('pager');
}
//Otherwise, show no results
else {
$content = "No blog posts found.";
}
//Finally, we declare a block and assign it the content
$block = new stdClass();
$block->title = 'Blog Posts';
$block->content = $content;
return $block;
}
/**
* Function used for editing options on page. None needed.
* @author Austin DeVinney
*/
function blog_post_listing_edit_form($form, &$form_state) {
return $form;
}
Some things to note here. We're basically making a view by hand using EntityFieldQuery. It's a nifty way to write entity queries a bit easier and comes with some useful how to's on Drupal.org. We also offload all rendering to work with Display Suite and use the built-in pagination that Drupal provides. All things considered, I'm really happy with how this comes together.
Panels
Finally, we need to add this to the page manager with panels. Browser to Structure -> Pages -> Add custom page and it will provide you with a step by step process to make a new page. All we're going to do here is add our newly created content type to the panel, as shown here.
And now, we're all ready to export to the Feature we created. Go on back to and recreate the feature and you're ready to push your code live. After everything is said and done, you should have a working blog with pagination.
.
Motivation
Obviously, this example is extremely basic. We could have done this in a View in far less time. Why would we ever want to use this? That's a great question and I'd like to elaborate on why this is important. Views are great and solve this problem just as well. They export nicely with Features and can even play with Panels (if you want to use Views as blocks or content panes). That being said, this is more for the layout of how we would have custom code that works with a lot of Drupal's best practices. Imagine instead if we have a complicated third party API we're trying to query and have our "view" react to that. What if we want a small, code-driven block that we can place discretely with panels? The use cases go on, of course.
There are many ways to solve problems in Drupal. This is just my take on a very clean and minimal code structure that allows developers to be developers and drive things with their code, rather than being stuck clicking around in menus.
Tags: drupaldrupal 7ctoolspanelsfeaturestechnologymaintainability