Reply to comment
Output Buffering: The Magic of Captured HTML
Submitted by bingomanatee on 18 July, 2009 - 10:39One of the neatest and simplest tricks I've picked up lately is the use of the PHP native output buffering (OB) menu. I have always hated embedded/echoed markup, but found it necessary if you want to return a string rather than dump it to the screen.
Another possible use for output buffering is that you can push work to the screen for the benefit of the audience while you continue to compute page elements in the server. (a sort of "server side ajax".)
function make_a_link($pURL, $pLabel, $pClass)
{
return sprintf('<a href="%s" %s>%s</a>', $pURL, ($pClass? sprintf(' class="%s" ', $pClass) : ''), $pLabel);
}
while this does of course "work" in the most banal sense of the term, its not elegant or especially readable. When done in mass it is just plain bad, and ignores the ability of PHP to embed code inside markup (as opposed to the above which reverses the system, to the disadvantage of both.)
The alternative is to write a function that simply writes to the browser as its called:
function make_a_link($pURL, $pLabel, $pClass)
{
?>
<a href="<?= $pURL ?>" <?= $pClass? sprintf(' class="%s" ', $pClass) : '' ?> ><?= $pLabel ?></a>
<?
// NOTE: this function WRITES content to the browser but does not return it.
}
Note: <?= [value ] ?> is equivalent to <? echo [value] ?>.
This just reads a lot better to me -- especially in a syntax editor. In my experience when you mix code and markup, the marku tends to make up the bulk of the copy, so embedding (in place) the PHP as you need it is cleaner and easier to edit and interpret. What this gains in elegance, it loses in functionality; you don't always want to generate markup "just in time": often you want to store it to the database, save it in a cache, or further filter it -- perhaps, for instance, using the generated markup as a template for further processes.
Output buffering allows you to capture markup that would ordinarily be sent to the browser and return it as a string.
function make_a_link($pURL, $pLabel, $pClass)
{
ob_start();
?>
<a href="<?= $pURL ?>" <?= $pClass? sprintf(' class="%s" ', $pClass) : '' ?> ><?= $pLabel ?></a>
<?
return ob_get_clean(); // captures the above markup -- returns it as a string
}the "ob_start" function tells the PHP proccessor to capture all further entries to the browser to a "buffer". This storage place will pile up markup into a string that can be managed: you can, at any point you wish, echo it to the screen, dump the buffer to a variable, or simply erase it.
Calling this function (and ignoring its return value) would have NO EFFECT on the page. It captures its contribution and sends it back as a return value, which can be echoed, saved, put to a file, or whatever. I.e., this:
<p>I am to lazy to type links so I use a function to link
to <? make_a_link('http://www.wonderlandlabs.com', 'My Site', '') ?>.</p>
dumps
<p>I am to lazy to type links so I use a function to link to.</p>
to the browser. However,
<p>I am to lazy to type links so I use a function to link
to <? echo make_a_link('http://www.wonderlandlabs.com', 'My Site', '') ?>.</p>performs as expected:
<p>I am to lazy to type links so I use a function to link to <a href="http://www.wonderlandlabs.com">My Site</a>.</p>
These are the PHP functions to manage the output buffer.
- flush — Flush the output buffer
- ob_clean — Clean (erase) the output buffer
- ob_end_clean — Clean (erase) the output buffer and turn off output buffering
- ob_end_flush — Flush (send) the output buffer and turn off output buffering
- ob_flush — Flush (send) the output buffer
- ob_get_clean — Get current buffer contents and delete current output buffer [and turns off buffering]
- ob_get_contents — Return the contents of the output buffer
- ob_get_flush — Flush the output buffer, return it as a string and turn off output buffering
- ob_get_length — Return the length of the output buffer
- ob_get_level — Return the nesting level of the output buffering mechanism
- ob_get_status — Get status of output buffers
- ob_gzhandler — ob_start callback function to gzip output buffer
- ob_implicit_flush — Turn implicit flush on/off
- ob_list_handlers — List all output handlers in use
- ob_start — Turn on output buffering
- output_add_rewrite_var — Add URL rewriter values
- output_reset_rewrite_vars — Reset URL rewriter values
The ones I use regularly I've marked as bold. The ones I've never used are italicized. Note that sevaral verbs repeat throughout these functions:
- Flush -- write the buffer to the browser.
- Clean -- erase the buffer.
- End -- stop buffering
- Get -- return the buffer as a string
Its pretty easy to find the combination of actions you want in a pre-existing one-shot function.
Note that the output buffer is a stack; you can call ob_start more than once. If each ob_start() call is not balanced with an output buffer ending method (underlined), then some of your content will vanish. This might seem annoying but really its a good thing: it means that the buffer that is returned by the ob_get* methods is only the content from the most recent call of ob_start() onward. This means, for instance that if one buffering function calls another, for the most part, things work as you would expect.
function url($pRoot, $pPath, $pQueryParams = NULL)
{
ob_start(); ?>http://<?= $pRoot ?>/<?= $pPath ?>?<?
foreach($pQueryParams as $key => $value): ?><?=$key ?>=<?= $value ?>&<?
return ob_get_clean();
}
function make_a_link($pRoot, $pPath, $pQueryParams, $pLabel, $pClass)
{
ob_start();
?>
<a href="<?= url($pRoot, $pPath, $pQueryParams) ?>" <?= $pClass? sprintf(' class="%s" ', $pClass) : '' ?> ><?= $pLabel ?></a>
<?
return ob_get_clean(); // captures the above markup -- returns it as a string
}Note the url function opens a second buffer inside the make_a_link function. The outer (make_ a_link) buffer doesn't affect the inner buffer -- the sub-buffer of url works as expected no matter how deeply it's nested. However the echoing of url is still captured in the buffer context of make_a_link. This works like the "play within a play" in Hamlet -- from the perspective of the characters watching Hamlet's play it is fiction, but the actors are real. From our perspective everyone in hamlet is a fictional character, but once the play "Hamlet" is performed, both the main play and the play Hamlet writes is flattened to the same level of reality. That is,
function Hamlet()
{
ob_start();
?>
<h1>Hamlet</h1>
<h2>Act 1</h2>
<h3>Scene 1</h3>
<p>Oh Crap, Dead Hamlets Ghost!</p>
...
<p><b>Hamlet</b> Hey guys lets watch a play!</p>
<?= hamlets_inner_play() ?>
<p><b>Hamlet</b> Miss my dead dad much, mom? <cough>Regal whore much?</cough></p>
...
<h1>The End</h1>
<?
return ob_get_clean();
} // returns the entire play
function hamlets_inner_play()
{
ob_start() ?>
<h1>A Piece of Work</h1>
<p>...</p>
<h1>The End (of a Piece of Work) </h1>
<?
return ob_get_clean();
} // end of the mini-play
?>
This is one more example of why you don't need a templating system in PHP: for the most part, PHP IS a templating system. I wasted a lot of time with SMARTY and other similar languages; very little was gained from the experence. PHP was designed to work inside markup, and doing so makes it much friendlier to design oriented editors.
The Old School Buffer
There is another form of "capture" thats worth mentioning. (especially since searching for it in the PHP docs gives you an error on php.net
)
The "<<< [name]... anything ... [name]" impertive captures everything between the two instances of the same token and returns it as a string. You can put php code (that will not execute), javascript, whatever -- as long as you don't embed another occurance of [name]. This is useful for code generation, for instance, which is something I'll be writing about pretty darn soon.
example:
$my_text_block = <<< TEXT_BLOCK Dear Mr. $customer_name. Thank you for buying our $product. We hope that the $discomfort in your $body_part ends soon. Best Wishes, $ointment_company_name TEXT BLOCK; // note -- this is the completion of the very first line.
While it is in general better practice to pur resources in their own files, there is the odd situation where a block is better managed as a simple inline capture. Note that this is MUCH different than output buffering. It has nothing to do with the outward stream going to the browser -- it is just a string technique.
