Free your content of PHP: Moving PHP code out of blocks, views and nodes

From the early days, Drupal had the ability to embed PHP code in its content. This provides flexibility and functionality, most importantly, nodes and blocks can contain dynamically fetched data from the database using custom queries and displayed them in other content.

This is an easy approach to get such data without writing a module. All you need to do is assign the PHP input format filter to the node or block and paste your PHP code in it, and voila, you have dynamic content.

Code in Content: the issues

However, this approach is suboptimal and has serious drawbacks, including:

Lack of Readability and Clarity

Since the code is embedded within pieces of otherwise HTML content, it is not obvious where the code is, so tracking it is really hard. Moreover editing it in a textarea is far less friendly than in a prope text editor with syntax highlighting. This leads to maintenance issues with this code in the long run.

Potential Security Issues

Since the code is not readily visible, it is more prone to be missed in doing code reviews or security audits, than if it would have been in a more conventional repository. This is why modules such as Paranoia exist, and why Drupal 6 now has a separate PHP module that has
to be explicitly enabled in order to get the PHP filter functionality.

Lack of Version control

Nodes and blocks are never put in a versioned code repository, such as CVS, SVN or GIT. Because of that tracking changes is very hard, and maintenance can be a nightmare.

Performance Drawbacks

Code stored in the database is first fetched from the database, then executed by PHP. Because of that, this code is never cached by accelerators/op-code caches, and can be slower to execute than cached code.

Search Implications

Embedding PHP output within Drupal's search provides some interesting challenges.

    As you can see, there are several drawbacks here.

    Views is another module that stores pseudo-code in the database (actually query generator instructions). However, views offer the ability to export its queries as PHP code which can be made into modules and suffer none of the above drawbacks.

    Removing Code from Content

    For the above reasons, we went on a mission over the past few weeks to rid sites of code embedded in content. So far, we have done 5 sites, the last of which is Drupal.org itself.

    The process is described as follows:

    Get A List of all PHP Content

    First find out what is your PHP filter format ID. You can do this via the following SQL.

    mysql> SELECT * FROM filter_formats;
    +--------+---------------+-------+-------+
    | format | name          | roles | cache |
    +--------+---------------+-------+-------+
    |      1 | Filtered HTML | ,1,2, |     1 |
    |      2 | PHP code      |       |     0 |
    |      3 | Full HTML     |       |     1 |
    +--------+---------------+-------+-------+
    

    Now, we know that 2 is the PHP filter format.

    Next we estimate how many PHP nodes we have.

    SELECT n.format AS id, COUNT(*) as cnt 
    FROM node_revisions n 
    WHERE format = 2 
    GROUP BY n.format;
    +----+-----+
    | id | cnt |
    +----+-----+
    |  2 | 14 |
    +----+-----+ 
    

    And then we get the text of the nodes that have PHP in them, so can work on making it into modules. Note that some nodes may have the PHP format, but not PHP code in it, e.g. when you need to paste javascript verbatim.

    SELECT nid, body FROM node_revisions WHERE format = 2; 
    

    And then the blocks too. Again, not all the blocks may contain actual PHP code.

    SELECT * FROM boxes WHERE format = 2; 
    

    After that, we need to find blocks that have PHP visibility settings too, although the body may not have PHP:

    SELECT * 
    FROM blocks b INNER JOIN boxes x ON b.delta = x.bid 
    WHERE status = 1 AND pages LIKE '%<?php%'; 
    

    Now we have all the info we need, we then move to analysing it.

    Do Some Analysis

    Study each node and block and see whether you can use the code as is, or make it simpler as you refactor it and move it out from content to modules. This all depends on the actual code and what it does.

    Convert Blocks into Modules

    A basic block module will look like this, which should go in a file called something.module:

    function something_block($op = 'list', $delta = 0, $edit = array()) {
    switch ($op) {
    case 'list':
    $blocks[0]['info'] = t('Some block');
    return $blocks;
    case 'view':
    $block['subject'] = t('Some block');
    $block['content'] = something_output();
    return $block;
    }
    }
    function something_output() {
    // code goes here
    return $output;
    } 
    

    All you need is an something.info file to go with it, and you are all set.

    Whether you combine several blocks in one module or keep it separate depends on your particular specifics.

    Convert Dynamic Node Data into hook_nodeapi

    Now, you need to convert the code that was in nodes to become modules. You can chose to combine several nodes in one module so as to reduce the number of modules on your site.

    On Drupal.org, most nodes were there to preserve a place in the Handbook hierarchy, and provide dynamic data in them. In this case, we need to keep the specific nodes as they are, but change their format from PHP to Filtered HTML.

    Then we have a module like the one below that appends the dynamic content at the end of the node body. 

    function something_nodeapi(&$node, $op = 'view', $teaser = FALSE, $page = FALSE) {
    if ($op == 'view' && $page) {
    $extra = '';
    switch ($node->nid) {
    case 48380:
    $extra = something_output();
    break;
    case 257382:
    $extra = something_other();
    break;
    }
    $node->content['body']['#value'] .= $extra;
    }
    }
    function something_output() {
    // code goes here
    return $output;
    }
    function something_other() {
    // code goes here
    return $output;
    }
    

    Of course, you need an info file to go with the above module as well.

    Convert Views into Modules

    Using the export function of views, you should convert the views into modules. Check the views documentation for more info.

    Now you have the code versioned, visible, auditable, faster and more maintainable. You can sleep better now ...

    Contents: 

    Comments

    Extremely valuable info. I

    Extremely valuable info.

    I noticed this problem very quickly and set about ensuring that no code is actually located "in the database". I now instill this in my trainees.

    CCK Allowed Values

    Is it possible to create a module for CCK field values. For instance, with a select field you can populate the allowed values list with php code. However, if you try to do this in a module with hook_form_alter and populate the options of the select list, the values are not saved on node submit.

    Many thanks

    Excelent article, this will definetly make my life easier, and my work more professional.

    I am not a developer as per say, but I do need to code php blocks sometimes, and this way I can prevent the client from messing things up, and have a way to edit/maintain things that is much faster and comfortable.

    Never thought it was this easy! :D

    Greetings from Barcelona

    what about direct code injected in tpl files

    for instance.

    i often use module_invoke or similar to directly insert a view or block through code or even direct sql queries in page-front.tpl.php, to insert content how i want, where i want, when i want. i find it much easier, quicker and more maintainable then using panels, blocks, etc.

    do you have any thoughts on performance of inserting blocks of code or php output for presentation like this in the TPL files (NOT NODES) versus using the std drupal display mechanisms (blocks, panels, etc) via regions?

    also, if i insert a view or a block defined in a custom module directly in a tpl page like this, can i still take advantage of caching the block or view if it doesn't change (or hasn't changed since last time viewed)? if so, do i need to do anything special in my custom block or view code or is this somehow magically handled by the block system or views already? if something needs to be done, what?

    finally. are there any ways to see the actual SQL query statement that each view generates. I know i can export the view definition in views but i don't want the php definition. I merely want to see the output generated by each view. that can be either at run time or via a utility.

    thanks much in advance

    Answers

    Well, first off, placing views in the theme is not recommended. Anything that generates data should be in modules. Anything that displays and formats data should be in the theme. This way presentation and logic are separate.

    As for inserting blocks and views in the theme, this is kind of borderline where people would have different opinions.

    For seeing the SQL queries, install the devel module.
    --
    2bits -- Drupal consulting

    followup

    thanks khalid,

    outside of separating logic from presentation, any thoughts about the *performance* differences using either or methods i described above (regardless of whether you yourself condone directly inserting code in a tpl or not)?

    also. i know we as developers hold opinions about why we use one method versus another. some observations about why i do what i do.

    1) even the best drupal tpl file doesn't completely isolated code / data from presentation. it sounds like your take is to try to eliminate as much code as possible. understandable. in my case i am both the themer and the developer and i generally find there to be a lot less overhead and maintenance doing things directly in the tpl files.

    2) regarding #1, having everything about a front page in one place = the tpl file, is quick and allows for easy version control.

    that's why i asked the questions about the performance related to my approach. from a development / work standpoint, i completely prefer doing it my way.

    however, if i am losing something from a run-time performance perspective, then it might be worth it to go via a module approach.

    i don't think there's that much difference. php is php. it gets run regardless of where it's located. since directly doing queries is faster and eliminates drupal overhead, i think it may have benefits. the only main difference might be related to caching. an area i've not investigated much yet.

    thanks

    Design, architecture and simplicity

    This is not about performance, but rather about design.

    Simplicity and modularity have reasons beyond what developers call elegance. It is not only about aesthetics.

    When you architect something to be as simple as possible (as opposed to overly complex), and as modular as possible (separation of concerns, easy to understand, easy to modify, easy to refactor), it pays off in the long term with reduced maintenance.

    Also, remember the rule about premature optimization being evil. Down the line when you want to tune things for performance (modifying things, adding caching, ...etc.), it is easy to attack the problem when things are simple and modular, rather than when things are all mushed up together in an untanglable mess.

    As from a PHP point of view, if you are using an accelerator like APC, then it should be a moot point. Loading other files will happen just once, so separating the presentation from queries should not matter from a performance point of view.

    Architect it for simplicity and modularity and things will pay off in the future in terms of reduced maintenance and tuning for performance.
    --
    2bits -- Drupal consulting

    Agreed, but?

    Hi,

    I completely understand your approach, thanks for detailing it for us weaker mortals!

    I have once used PHP in a block and I hated every minute of writing it in the textarea! So I decided to add a PHP file include to the code I was writing instead, this meant I could write my PHP in my IDE and the code was in a file capable of being included in version control. My include was in my theme folder, but as it's an include it could go anywhere, even core includes folder, (which shouldn't affect CVS updates to core) to satisfy your separation of data and presentation requirements.

    Is this approach remotely acceptable or do we just agree that the PHP input filter is the gate to temptation of evil doings?

    luke

    Depends

    If your code snippets are only for presentation, then it is acceptable to do some of that in the theme, better yet, add them as functions in template.php.

    If your code contain logic and database queries, then it is best to split them into a module for the site, to separate logic from presentation.

    You should not be changing the core theme anyway. Copy it to a new directory, e.g. mytheme, then do the changes there. This way it does not get overwritten when you upgrade Drupal.

    Nor should you add your code under includes or modules, for the same reason. Your code should be under sites/all/modules or sites/default/modules or sites/example.com/modules. Your theme should be under sites/all/themes or sites/default/themes or sites/example.com/themes.

    Mister misunderstood

    Hi,

    Yes I agree with that too, I think you misunderstood what I meant (sorry if I wasn't that clear :-)). I know it is massive no no to touch core, and yes that means core themes too, I simply meant that the accessibility/manageability of using an include (and putting it anywhere, but obviously somewhere sensible and following Drupal standards) is far greater than adding loads of PHP into the database. I do prefer your nodeapi method though, which is what I'll be using in the future.

    luke