drupal

Working with Arrays and Objects in Drupal with print_r() and dpm()

Tags: 

When customizing things in Drupal, whether we like it or not, there often comes the need to dive in and alter or override things. Because of Drupal's API, we're given a lot of wonderful hooks and theme functions to take advantage of without touching Drupal core. Instead we can modify most things the way we want within our own custom module or theme, without ever having to worry about our changes getting overwritten when doing an update to our web site.

Great! There's plenty of tutorials, blog posts, forum posts and issues both on drupal.org and throughout the interwebs on how to work with data in order to bend it to our wills, but it's not always clear on how exactly to work with the data in the first place.

Understanding print_r()

PHP provides us with a handy (if not a bit "dirty") function called print_r. What it does is print readable information about PHP objects and arrays that assist with debugging and figuring out what value we're trying to target. Here is a partial output of me doing a print_r($form) in a hook_form_alter of a blog post. What we need to understand is I am printing the full output of $form, which is an array. Each piece of the array contains a key and a value, where the value can be another array, an object, or a string.

Take a look at the screenshot above. When I am manipulating this form, I need to target the keys of the array(s) in order to manipulate any of the values. Each indent shows us the tree. We know that this is printing out $form. In order to target any of the keys within that, we need to use $form['nid'] or $form['type'] or $form['uid'], and deeper than that is $form['nid']['#type'], $form['type']['#value'], or $form['uid']['#value'].

Now we understand that arrays are handled with brackets: [ ]

But PHP objects are a little different. Below is a screenshot of another part of the $form array I posted above. This portion is accessed with $form['#node']. You'll notice instead of Array, it says stdClass Object. This means we're working with an object, not an array. Let's ignore what the differences are right now, and just figure out how we manipulate that data.

Arrays are targetted with brackets. Objects are targetted with ->.

Homework: A few other useful PHP functions are var_dump() and var_export().

Using dpm()

The Devel module takes this a bit further and provides us with dpm(). This gives us output in a much more readable format, though the concept is the same.

With this, you can now use many of Drupal's great hooks to override various content-- take a look at my previous blog posts, Drupal Alters and Overrides: hook_form_alter and Drupal Alters and Overrides: hook_menu_alter.

Drupal Alters and Overrides: Drupal.theme.prototype

Tags: 

Drupal.theme.prototype was introduced in Drupal 6, meant for modules producing HTML content within JavaScript, allowing that this content to be overridden.

As a real world example, the Node relationships module produces a link via jQuery that, on mouse over, pops up the words Search and reference.... Each dynamically generated link has it's alt tags created in this way.

To change this link without modifying the module directly, you can override the text in your own theme's template.php file.

The original code in Node relationship's node_form.js file contains:

// Install the "Search and reference" button.
    if (fieldOptions.searchUrl) {
      var $searchButton = $(Drupal.theme('nodeRelationshipsReferenceButton', 'search', Drupal.t('Search and reference...')));
      $buttonsWrapper.append($searchButton);
...

Notice Drupal.theme namespace above. What Drupal.theme is doing is passing a few parameters; the first, nodeRelationshipsReferenceButton, is the name of our actual theme function which contains:

/**
* Theme the specified button for an autocomplete widget.
*/
Drupal.theme.prototype.nodeRelationshipsReferenceButton = function(type, title) {
  return '';
};

The other parameters being passed are type and title.

In your theme's template.php file, you could use the code below to change the text to Search.

Drupal.theme.nodeRelationshipsReferenceButton = function(type, title) {
  if (type == 'search') {
    title = 'Search';
  }
  return '';
};

The only real difference between these functions is the removal of the prototype namespace.

Drupal Alters and Overrides: hook_form_alter

Tags: 

One of the first rules most people coming into the world of Drupal learn, hopefully, is to not hack core. I certainly didn't know about this when I first started out and hacked to pieces a lovely Drupal 4.6 site which, amusingly enough, still exists today. Since then, though, I've become more accustomed to the various ways of altering and overriding things in both Drupal core and contrib "the Drupal way".

Because Drupal is built modular with a strong hook API system, it gives us a lot of power in terms of changing things through our own custom modules. You can read more about creating a custom module at http://drupal.org/developing/modules -- there is also a useful module with some good examples you can download from http://drupal.org/project/examples

I'm going to go through a few different ways you can alter and override content in my next series of blog posts. This first one will cover one of the most popular.

hook_form_alter

One of the most common hooks used to override or alter things in Drupal is hook_form_alter. Drupal is driven by forms-- administration settings forms, content creation forms, contact forms, menu forms, user registration forms, etc., that I don't think I've ever worked on a Drupal site where this hook wasn't used.

Let's say, for example, you've got yourself a View with some exposed filters. By default the submit button says "Apply", but you've been asked to change that to "Submit".

My custom module is called demo. If I take a look at the link above from api.drupal.org, it tells me the parameters my function should have:

hook_form_alter(&$form, &$form_state, $form_id)

This is how it would appear in my module:

function demo_form_alter(&$form, &$form_state, $form_id) {

}

I need to find the $form_id in order to make sure I'm altering only that specific form and not the others. I usually do this just by printing out $form_id. For Views exposed filters, they all have the same $form_id which is 'views_exposed_form'. In order to make sure I'm only targetting this specific exposed form, I also use the $form['#id'] value. You can get these values by doing a print_r($form) or dpm($form) in your module. My alter now looks like this:

/**
 * Implementation of hook_form_alter().
 */
function demo_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'views_exposed_form' && $form['#id'] == 'views-exposed-form-content-search-page-1') {
    $form['submit']['#value'] = t('Search');
  }
}

I can do all sorts of things with forms here. I can remove elements, rename them, add new ones. I can also add or override submit and validation functions. Here are a few examples:

/**
 * Implementation of hook_form_alter().
 */
function demo_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'views_exposed_form' && $form['#id'] == 'views-exposed-form-content-search-page-1') {
    // Rename the submit button
    $form['submit']['#value'] = t('Search');
  }
  if ($form_id == 'search_block_form') {
    // Add default "Search" text in the searchbox
    $form['search_block_form']['#default_value'] = t('Search');
  }
  if ($form_id == 'page_node_form') {
    // Remove the "Preview" option
    unset($form['buttons']['preview']);
    // Add my extra validation function
    $form['#validate'][] = 'demo_extra_validate';
  }
}

Sneezing Alligators Come to the Gardens

Tags: 

If you've stumbled your way here, welcome. While I've had the great opportunity to support, use and play with Drupal Gardens the last few months, the number of sites I've built have primary been used for testing. This, here, is my first "real" site, that will soon replace my current blog at http://alligatorsneeze.com

I'm one of those developers that even when I might have the time, there's almost always more pressing matters, other sites to break or fix, or a multitude of other tasks piled upon my to-do list that working on my own personal site and blog is almost always at the very bottom of the list. Because of this, Drupal Gardens is a perfect choice for me. It allows me to quickly put a site up, use the awesome theme builder to more easily customize the look and feel of my site, and I don't have to worry about updating it or keeping it maintained.

Sounds perfect, right?

We'll see. I very much realize it's still in beta, and that Drupal 7 is still in alpha. I suspect I'll continue to run into issues. But I'm having a lot of fun building out this site. I've got my banner working after a couple snags and will be working more on the way the site looks. Being that I have very little design background or sense, we'll see how that plays out...

Optimizing mySQL: Temp Tables on Disk

Tags: 

I wrote this back in June when I was knee deep in attempting to optimize a Drupal 5 website that was receiving a lot of traffic and showing very poor performance. This article is incomplete and I don't really come up with any solid solutions here, but thought this may be of some help nonetheless.

Performance of any site is a concern when you start getting actual traffic. Drupal performance can be even trickier, as it often likes to hog up server resources especially when you have a lot of modules enabled.

Often the issue lies with mySQL. While I like mySQL, configuring for optimal performance doesn't always come easy. One issue that can often occur is a large number of temporary tables being written to disk. What we want is for these tables to be written to memory. A good article which discusses the overhead of mySQL writing to disk can be found here at http://www.mysqlperformanceblog.com/2007/08/16/how-much-overhead-is-caus...

Essentially, it says MEMORY temporary tables can be 10-100 times faster than disk based MyISAM tables . That's quite a difference.

I started two or three weeks ago tweaking our mySQL configuration and performance on a site that was getting 85% or more of it's temp tables written to disk. Today, we're getting between 6-10%. There's still work to do, but the increase has been awesome and very noticeable in the speed of the site.

To check the status of your temp tables, in a mySQL prompt, type show status. This will show you all the variables mySQL has set, including:


| Created_tmp_disk_tables | 141880 |
| Created_tmp_files | 464 |
| Created_tmp_tables | 1304743 |

As you can see here, we still have quite a lot of our temporary tables writing to disk.

You may also want to use something like this script. Just put it on your server and run it with sh tuning-primer.sh and it'll give you some great tips overall, as well as tell you more about your temp tables.

The first method to stop this from happening is to increase your table_cache. A formula often used is:


table_cache = opened table / max_used_connection

Getting this up to a good number is key. Obviously, you can't just keep raising this, though. This value, and most others within mySQL, are going to be limited by your server's RAM.

Other values you need to look at are tmp_table_size, max_heap_table_size, key_buffer, sort_buffer_size, read_buffer_size, myisam_sort_buffer_size.

One problem you may encounter, though, is that blob and text column types are always written to disk. There are a few Drupal patches I ended up testing, benchmarking, and applying in order to fix some of these issues.

http://drupal.org/node/109513 converts our temp tables to the HEAP engine type over MyISAM. This alone was a huge improvement.

You can also turn on mySQL slow query log. To do so, simply add these lines to your my.cnf:


log-slow-queries=/var/lib/mysqllogs/slow-log
long_query_time=2
log-queries-not-using-indexes

The long_query_time is the queries you want to log longer than 2 seconds, or whatever value you might want to use. I'd increase this to start with to track down the worst queries. We started it at 5 seconds.

The log-queries-not-using-indexes will also log queries not using indexes, of course.

By using our slow query log, we found a few queries which were desperately in need of optimization. A very handy tool in defining whether a query is causing issues is by using the EXPLAIN command. This can be used by simply typing this into your mySQL command line prompt:


EXPLAIN SELECT DISTINCT(n.nid), e.event_start FROM node n INNER JOIN event e ON n.nid = e.nid INNER JOIN node_access na ON na.nid = n.nid WHERE (na.grant_view >= 1 AND ((na.gid = 0 AND na.realm = 'all') OR (na.gid = 1 AND na.realm = 'content_access_rid'))) AND ((n.moderate != 1)) AND ( n.status = 1 AND ((e.event_start >= 457516800 AND e.event_start = 457516800 AND e.event_end = 460195199)) ) ORDER BY event_start;

And we were given results that look like this:


+----+-------------+-------+--------+-------------------------------------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-------------------------------------
| 1 | SIMPLE | na | ALL | PRIMARY | NULL | NULL | NULL | 49571 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | n | ref | PRIMARY,status,node_moderate,node_status_type,nid | nid | 4 | brenda2_06_02_08.na.nid | 1 | Using where |
| 1 | SIMPLE | e | eq_ref | PRIMARY,event_start | PRIMARY | 4 | brenda2_06_02_08.na.nid | 1 | Using where |
+----+-------------+-------+--------+--------------------------------------

Notice the Using where; Using temporary; Using filesort part. This tells us right there it's using a temporary table.

You can also use show processlist to get a list of what's currently going on.

For example gives me this row:


| 1237 | XXX | localhost | database | Query | 1 | Copying to tmp table | SELECT DISTINCT(node.nid), node.created AS node_created_created, users.name AS users_name, users.ui |

This will only show you things currently happening, though.

Now that we know this query is bad, we can work on rewriting it.

Pages

Subscribe to RSS - drupal