Developing With Views Part I: Describing your data to Views

Dec 19, 2011 · by Julia Evans

Views provides a really great interface for displaying data on your website. However, developing with Views can be confusing at first. I’m going to explain the basics of developing with Views, and how to tell Views about your own custom tables.

I’ve also found delving into the Views source code has been really helpful to me in figuring out how Views things work, so we’re going to take a look at the guts of Views. It turns out they’re not so scary, after all!

Any examples here are for Views 3 and Drupal 7.

How does Views work?

The first important thing to understand is that for every view, Views executes a (possibly gigantic) SQL query. If you go to /admin/structure/views and click on ‘Settings’, you can turn on a setting called “Show the SQL query”. This will allow you to view the SQL query that Views has constructed for any previewed view.

Here’s an example from a simple view:

SELECT 
    node.title AS node_title, 
    node.nid AS nid, 
    node.created AS node_created
FROM 
    {node} node
WHERE 
    (( (node.status = '1') AND (node.type IN  ('article')) ))
ORDER BY 
    node_created DESC
LIMIT 10 OFFSET 0

This corresponds to the configuration:

In this example, what Views has done is:

  1. Start with a base table (node)
  2. JOIN some tables to it (node_content_revision)
  3. Add WHERE and ORDER BY clauses
  4. Display everything!

When extending Views, you can customize almost anything. In particular, you
can tell Views about a new base table (via hook_views_data(_alter)) add more
WHERE and ORDER BY clauses (filter and sort handlers) render the displayed
fields differently (field handlers)

For now, we’ll just discuss the first one -- we’ll discuss the other two in
later blog posts.

Telling Views about your database: hook_views_data

Views stores all of the information about how the Drupal database is set up (columns, relationships between tables, etc.) in a giant array called $data. You can add things to this array using hook_views_data[_alter]. This is where you tell Views about any new tables you need to display data from, and how these tables relate to each other through joins and relationships.

Views in general knows how to deal with data in tables: all the basic SQL-generating code is already there. If you just want to display or filter by your data, all you need to do is set up Views in the right way.

Let’s dive into the Views source code for an example. This is going to be a huge block of code, but it turns out to be quite approachable. If we look at node.views.inc, we see:

Diving into the Views source code

We're going to go through this step by step.

 /**
 * Implements hook_views_data()
 */
function node_views_data() {
  // ----------------------------------------------------------------
  // node table -- basic table information.

  // Define the base group of this table. Fields that don't
  // have a group defined will go into this field by default.
  $data['node']['table']['group']  = t('Content');

  // Advertise this table as a possible base table
  $data['node']['table']['base'] = array(
    'field' => 'nid',
    'title' => t('Content'),
    'weight' => -10,
    'access query tag' => 'node_access',
    'defaults' => array(
      'field' => 'title',
    ),
  );

  // For other base tables, explain how we join
  $data['node']['table']['join'] = array(
    // this explains how the 'node' table (named in the line above)
    // links toward the node_revision table.
    'node_revision' => array(
      'handler' => 'views_join', // this is actually optional
      'left_table' => 'node_revision', // Because this is a direct link it could be left out.
      'left_field' => 'nid',
      'field' => 'nid',
      // also supported:
      // 'type' => 'INNER',
      // 'extra' => array(array('field' => 'fieldname', 'value' => 'value', 'operator' => '='))
      //   Unfortunately, you can't specify other tables here, but you can construct
      //   alternative joins in the handlers that can do that.
      // 'table' => 'the actual name of this table in the database',
     ),
  );

  // ----------------------------------------------------------------
  // node table -- fields
  // title
  // This definition has more items in it than it needs to as an example.
  $data['node']['title'] = array(
    'title' => t('Title'), // The item it appears as on the UI,
    'help' => t('The content title.'), // The help that appears on the UI,
     // Information for displaying a title as a field
    'field' => array(
      'field' => 'title', // the real field. This could be left out since it is the same.
      'group' => t('Content'), // The group it appears in on the UI. Could be left out.
      'handler' => 'views_handler_field_node',
      'click sortable' => TRUE,
      'link_to_node default' => TRUE,
     ),
    'sort' => array(
      'handler' => 'views_handler_sort',
    ),
    // Information for accepting a title as a filter
    'filter' => array(
      'handler' => 'views_handler_filter_string',
    ),
    'argument' => array(
      'handler' => 'views_handler_argument_string',
    ),
  );

Even without any explanation, this is fairly helpful and well-documented. If we wanted to make a new base table, we could just copy this and change a few things. See Describing Tables to Views in the Views help for an in-depth description of how all of this works.

Let’s go through this code excerpt.

All of this is defined in hook_views_data().

$data['node']['table']['group']  = t('Content');

This is the name of the group node data belongs to: every field, sort, or handler defined by node is prefixed by ‘Content: ‘.

$data['node']['table']['base'] = array(...)

This defines node as a base table. The first step when creating a View is to choose a base table: the default is ‘Content’, which corresponds to the node table. The base table is the table which every other table is joined to when creating a view (node LEFT JOIN node_content_revision LEFT JOIN user …).

$data['node']['table']['join'] = array(...)

Here the way node joins to other base tables is defined: it explains how to join the node table to the node_revision table when node_revision is the base table. Describing Tables to Views explains how to set up this array, and even has a diagram!

$data['node']['title'] = array(...)

There are lots of entries like this. Each one looks like

$data[$tablename][$columnname] = array(...)

This tells Views about a new column, which can have an associated field, sort, contextual filter (argument), filter, and relationship. $columnname can either be the name of a column in $tablename, or anything you choose. If it is not the name of a column, you’ll need to specify an actual column name with ‘real field’. For example, if we wanted to add a random sort on the ‘title’ column, we could write

$data['node']['randomtitlesort'] = array(
  'real field' => 'title',
  'title' => "Random title sort",
  'sort' => array(
    'handler' => 'views_handler_sort_random',
  ),
);

The entry

'field' => array(
      'field' => 'title', // the real field. This could be left out since it is the same.
      'group' => t('Content'), // The group it appears in on the UI. Could be left out.
      'handler' => 'views_handler_field_node',
      'click sortable' => TRUE,
      'link_to_node default' => TRUE,
     ),

adds a field to the ‘Fields’ section in Views. This one is called ‘Content: Title’. There is also a sort, a filter, and an argument (contextual filter). As you can see, at minimum each needs a handler which tells Views what to do. This entire array is available to the ‘views_handler_field_node’ handler, and the ‘click sortable’ and ‘link_to_node default’ are options which configure this handler. Each handler’s options are documented at the top of the file defining the handler.

For example, http://views.doc.logrus.com/classviews__handler__field.html says that the base views_handler_field takes the options ‘click sortable’ and ‘additional fields’. It’s important to make sure you’re looking in the right version of Views: the ‘link_to_node default’ option doesn’t exist in Views 2, for example.

Resources

When I first went to the Views project page and saw the sentence “Views 3 documentation hasn't been written yet.”, I was pretty worried. How was I supposed to write code extending Views? There’s no need to despair, though. Much of the Views 2 doxygen documentation (http://views.doc.logrus.com) still applies to Views 3, and it’s a really great site to click around in.

In particular, I’ve found the following pages really helpful to keep around

I gave a presentation on this at DrupalCamp NYC: the slides. Here's some sample code that I wrote to make the slides.

There’s also the #drupal-views IRC channel on freenode.

That’s all for now! In Part II, I’ll explain how to write your own custom Views handlers, for example to do a custom sort on some data or display a complicated field.

Featured Articles