Using PHP Output Buffering In Templating
In a project I’m working on, I needed to render a Vue application inside a Drupal 7 (D7) site. Drupal handled the routing and asset loading while Vue handled everything else. It was important to try and not use Drupal for anything other than routing since D7 is approaching EOL in 2021. It was also an exercise in using more core PHP functionality rather than doing what I’m used to and snuggling up inside a Drupal function blanket for most coding tasks.
I was so far up D7’s arse, I even thought asset loading had to be done within a D7 menu callback using something like drupal_add_js()
or drupal_add_css()
. I now know that I could accomplish my task by only using a few hook_menu()
routing entries pointing to a few callbacks. I’m not saying you should leave out Drupal functions while writing your code, but in my specific case, it was useful to make my code portable for now. Chances are that you can start taking the framework out of your code, and if you think you can going forward, then you should.
I will say that I had fun taking the CMS out of my code while working on my solution and that I learned a bit more about core PHP functionality I’ll remember just in case it’s useful in the future. In the rest of this post, I’ll go through my frustration when initially trying to organize my code and pass data into template files and how PHP’s output buffering functions came to the rescue.
Rendering Output Without Using The CMS
When you run a PHP script, the code executes and the output is sent out to whatever initiated the script to handle the results. I click a link on a website, you run the code and hand me back a page. The PHP script runs through a set of functions and at the end, some function echoes out all the accumulated output as HTML.
Normally in a CMS, a routing system will hand off to a controller that prepares data and then hands it to a theming layer to construct the HTML that is sent back to the browser. If you don’t want to use the theme layer of the CMS, then you have to figure out how you want to include template files in order to hand back output to the CMS. In my case, I certainly did not want to use Drupal theming functions to render the Vue templates, but I had a little bit of a hard time figuring out how to pass data into my template files.
// In a hook_menu() routing callback...// Initialize output container.
$output = '';// Do data thing.
$data = json_encode($smart_thing->get_data());// Include some template files using $data as variables.
include 'templates/vue.tpl.php';
include 'templates/component_one.vue.php';
include 'templates/component_two.vue.php';// Return output to the CMS...but where is output aggregated?
return $output;// In templates/component_one.vue.php
<style>...</style>
<template>...</template>
<script>
var data = <?php echo $data; ?>;
Vue.component('component-one', {...});
</script>
I basically wanted to use my own set of services to manipulate and gather data and then let Vue take over in the template files with some data inserted server-side before the page loads. This was the simplest way I could see to organize my template files and pass data into them.
The template files I created had a vue.php
extension to them, and they were meant to resemble tpl.php
files you would use within D7’s theme layer. All of the parts of a Vue Single File Component are included: CSS styles, the template, and the accompanying component definition attached to Vue via Vue.component()
.
But when I tried to include the template files all I saw on the page was a string of all the code in the template file. There was no way for me to capture the output into a variable and then hand the HTML back to Drupal in order for it to does its thing and get around to sending something back to the user eventually.
Use Output Buffering To Organize Template Files
The missing piece of the equation for me was a concept called output buffering. Instead of immediately sending output back to the caller of the script, the output buffer holds the output hostage allowing for any other PHP code to interact with it before returning the output to its rightful recipient.
In my case, I totally wanted to hold the included template files into a buffer that I could grab the contents of and return as a single string of HTML when the time came. I needed to pass in variables to the component template files, but I didn’t want to include all of the component template files in one callback function. That would be very messy and hard to maintain.
// Start output buffer.
ob_start();// Initialize output container.
$output = '';// Do data thing.
$data = json_encode($smart_thing->get_data());// Include some template files using $data as variables.
include 'templates/vue.tpl.php';
include 'templates/component_one.vue.php';
include 'templates/component_two.vue.php';// Get contents of buffer including how $data is used inside of templates.
$output = ob_get_contents();// Close the buffer and clear the contents.
ob_end_clean();// Return HTML back to the CMS.
return $output;// In templates/component_one.vue.php
<style>...</style>
<template>...</template>
<script>
var data = <?php echo $data; ?>;
Vue.component('component-one', {...});
</script>
As you can see, the code from my first attempt remains mostly the same except that it is wrapped in output buffering functions. In this way, the buffer holds the output of the included files until I need to grab them. I could have done all sorts of things with the aggregated output before grabbing it to return to the CMS, but in my case, all I needed to do was pass some variables to be printed inside some components.
Drupal 7’s Use of Output Buffering For Themes
When I first started writing this post, I was thinking: “This isn’t the right way to do anything…I should be doing this in a more best practice way…I bet only hacky people use output buffering for templating’s sake.”
Well, I have a lot of respect for the Drupal core team members so when I saw ob_start()
in the theme layer, I didn’t feel so bad. If you’re not convinced by that and you say “Drupal sux” well then maybe you’ll be convinced when you see it used in Twig’s cached template output. Everybody in PHP loves Symfony so that made me feel even more like I was not doing anything entirely stupid.
// Drupal 7’s theme layer.
function theme_render_template($template_file, $variables) {
// Extract the variables to a local namespace
extract($variables, EXTR_SKIP);
// Start output buffering
ob_start();
// Include the template file
include DRUPAL_ROOT . '/' . $template_file;
// End buffering and return its contents
return ob_get_clean();
}// In a Twig cached template file...
protected function doDisplay(...) {
...setup code
ob_start();
...more code
// Last line of function.
echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));
}
As you can see, the Drupal theme function is doing basically the exact same thing as I am doing in my code except that it nicely extracts the variables from the $variables
array. I don’t like the extract()
function, but that’s another story. ob_get_clean()
combines my use of ob_get_contents()
and ob_end_clean()
. I prefer to be more explicit with what my code is doing, and I think it is clearer when you split getting the contents of the buffer away from closing it down. Since ob_start()
has no similar equivalent, why not match a closing function to it instead of combining it with gathering contents? But which output buffering functions you choose to use is up to you. I don’t think it goes beyond a stylistic preference, but there can be issues with memory management and ob_get_contents()
.
I’m less sure about what’s going on with the Twig template, but it is echoing out some HTML and looks like it is inserting variables amongst the echoing statements. Without using the output buffering functions here, I think you would have to $output .= '<p>something</p>';
your way through the template, and stringing together HTML output like that sucks.
Commonly Used For Setting Headers and Cookies
The most common use case for output buffering in PHP is for sending header information after the request has started. When PHP is preparing a response to send data back to the browser, it groups the output in chunks so that the header information comes before any output. I have little knowledge of this area so I’ll let a great StackOverflow answer speak for me.
The page/output always follows the headers. PHP has to pass the headers to the webserver first. It can only do that once. After the double linebreak it can nevermore amend them.
When PHP receives the first output (
echo
,<html>
) it will flush all collected headers. Afterwards it can send all the output it wants. But sending further HTTP headers is impossible then.
One common example is to set header information after performing some logic or gathering the first output sent to the browser via an HTML tag or an echo
statement.
/**
* Invokes hook_boot(), initializes locking system, and sends HTTP headers.
*/
function _drupal_bootstrap_page_header() {
bootstrap_invoke_all('boot');
if (!drupal_is_cli()) {
ob_start();
drupal_page_header();
}
}
In Drupal 7, an output buffer is started and then Drupal takes over to handle setting header output inside the buffer. It is confusing to figure out where the output buffer is closed, but with functions like drupal_add_http_header()
it would be easy for a developer to try and modify a header after some output has been generated by a script.
<?php
...stuff
?><?php
header("Content-type: text/html");
?>
Even that blank line in between the PHP tags is considered output and will cause an error when the header()
function is used in the next code block.
WordPress Turns It Up To 11
When I first started writing this post, I didn’t have any examples of where output buffering was used inside of a CMS. I was just learning about basic PHP functionality that matched my use case and needs. I showed you where output buffering is used in Drupal 7 and in Twig templates, but my first example of output buffering within a PHP framework came from WordPress (WP)…and they really like to use output buffering in scary ways a lot of the time.
To be fair, sometimes that is the only way for a WP developer to change the output that has already been printed to the screen by another output buffer. Since output buffers are used all over the place, it’s also necessary to use them in order to return your output to the parent output buffer.
What do I mean by “parent output buffer”? The plot thickens…Output buffering in PHP would be a lot more boring if you could only have one buffer going at a time. However, you can actually nest the buffers within each other.
ob_start();
echo ob_get_level();
echo '<p>foo</p>';
//...lots of stuff happens...
ob_start();
echo ob_get_level();
echo '<p>bar.</p>';
$out_2 = ob_get_contents();
ob_end_clean();
echo ob_get_level();
$out_1 = ob_get_contents();
ob_end_clean();
echo $out_1;
echo "\n";
echo $out_2;// Prints the following...
1<p>foo</p>1
2<p>bar.</p>
Each bit of content echoed existed in a different buffer level before it was output to the screen eventually. I believe that PHP closes and flushes the output of all unclosed buffers at the end of script execution, but the nesting levels allow for the return of buffered output that may contain HTML or whitespace and still have WP not send any output back to the browser.
final public function render( $container_context = array() ) {
$partial = $this;
$rendered = false;
if (!empty( $this->render_callback)) {
ob_start();
$return_render = call_user_func( $this->render_callback, $this, $container_context );
$ob_render = ob_get_clean();
if ( null !== $return_render && '' !== $ob_render ) {
_doing_it_wrong( __FUNCTION__, __( 'Partial render must echo the content or return the content string (or array), but not both.' ), '4.5.0' );
}
/*
* Note that the string return takes precedence because the
* $ob_render may just\ include PHP warnings or notices.
*/
$rendered = null !== $return_render ? $return_render : $ob_render;
}
In that code snippet, if the rendered output is not null
then it is returned to the caller. If it is null
then the buffer is returned which can contain PHP notices or warnings.
This stipulation brings up another good point and use of output buffering. Have you ever been to a site where something goes wrong in the code and you see some PHP warnings on a half-working page? I know I have. When output buffering is used, you can capture those notices so that they aren’t emitted to the user. Instead, you can send them a nicer message when an error is encountered.
I think I saw the use of that method in D7’s codebase, but I forget where that was. In lieu of an example there, I’ll give you a resource that has examples of where and how WP developers use output buffering in themes to purportedly “help developers prevent conflicts.” I don’t know what that means, but I think looking at some of the examples will expand your knowledge on output buffering.
Shoddy Vue Single File Components
Notice that I used the word “shoddy” here. While I was working on trying to include Vue components within a PHP-based CMS all on the server-side, I ended up with a solution that looked a lot like Vue’s Single File Components, and I think SFC are one of the killer features of Vue.
My “solution” is shoddy since it doesn’t cover a lot of what you get from node CLI tools while developing and then building the app for production. I don’t care much about “hot re-loading” since PHP requests are stateless and the code is all in the HTML of the page. To make my code more like what you get from node and Vue SFC, I was about to try and add some optimizations akin to a production build before I stopped myself from most likely wasting time.
// In templates/component_one.vue.php
<script type="text/x-template">...</script>
<script type="text/javascript">
var data = <?php echo $data; ?>;
Vue.component('component-one', {...});
</script>
<style>...</style>// In a Vue SFC.
<template>...</template>
<script>
import { component_one_data } from '../vuex/store';
export default {
name: 'component-one',
data() { return component_one_data },
...}
</script>
<style scoped>...</style>
If I change the script tag to a template tag, use Vuex instead of printing a $data
variable, export the Vue component object, and add “scoped” to my style tag, then I basically have a Vue SFC. This makes whatever I am doing in my templates portable and easy to use in a full Vue setup generated by vue-cli
. I thought that was nifty and one of the great reasons to use Vue: it is easy to add it to a single page, a group of included template files, and a stand-alone SPA.
One of the downsides of including the data the way that I am and joining together multiple template files in a response is the amount of HTML generated. Depending on the variables used, there could be a lot of characters in the response that could be fetched via an AJAX call. The templates aren’t minified either so a lot of whitespace between HTML or PHP tags can get included in the final response. Finally, the CSS can’t be scoped like in a Vue SFC so dealing with CSS in the templates can be tricky and CSS rules can easily become verbose and not shared amongst components.
You can actually mitigate these concerns with output buffering since you can do whatever you want to the output before sending it back to the browser. I almost started to try and “scope” the CSS in my templates by grabbing the buffer contents after including a template file and prefixing the style rules and classes with a hash ID. I also was going to remove whitespace and try to minify the size of the output in the same fashion.
I think I would have wasted time trying to perfect scoped CSS and minified code, but the fact that I can shoddily handle those concerns is kind of cool. I hope you’ve learned more about the PHP output buffering functions, how you can use them in templating work, how you can use them to better send out headers and change cookies, and some places in PHP projects where output buffering is used. When the time comes, you may need to summon the powers of the output buffer. Don’t be scared 😨, it’ll be just fine!