Archive for December, 2007

Handling templates with output buffering

Monday, December 17th, 2007

I just read a blog entry by Arnold Daniels about handling templates in PHP using output buffering with a callback function.

I’ve previously used a somewhat similar approach to templating using output buffering and register_shutdown_function, but using a callback function when starting output buffering is an interesting concept I hadn’t thought of so I had to look into it.

I found his solution a little lacking, so I created my own version of it. After I had created my own version though, I read a follow-up article on the same blog with an improved version of his solution. It turned out that my version was almost identical to the version in the follow-up article (even to the point that we both implemented static methods/members which the first article didn’t do). I guess great minds really do think alike. ;)

The differences between mine and his are basically too few and unimportant to justify me blogging about it, but it’s a little late for that now.. I found his second article after doing some more research after I had started writing this post, and I might as well finish it now. I’ll be short though.

My version

  • implements the singleton pattern
  • have header and footer as separate options
  • does not implement the mark/endmark methods introduced in Daniels second version (which I’ve found no need for)

Anyway, here it is, with a very simple usage example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class OutputHandler
{
	private static $instance;
	private static $header;
	private static $footer;
	private static $templateVars = array();
	private function __construct () {}
	final private function __clone() {}
	public static function getInstance()
	{
		if (self::$instance instanceof self === false) {
			self::$instance = new self;
			$args = func_get_args();
			self::$header = (isset($args[0]) ? $args[0] : '');
			self::$footer = (isset($args[1]) ? $args[1] : '');
			ob_start(array(__CLASS__, 'callback'));
		}
		return self::$instance;
	}
	public static function callback ($buffer, $flags)
	{
		if ($flags & PHP_OUTPUT_HANDLER_END) {
			$buffer = self::parseTemplate(self::$header) . self::parseTemplate($buffer) . self::parseTemplate(self::$footer);
		}
		return $buffer;
	}
	private static function parseTemplate ($template)
	{
		return str_replace(array_keys(self::$templateVars), array_values(self::$templateVars), $template);
	}
	public static function setTemplateVar ($key, $var)
	{
		self::$templateVars[$key] = $var;
	}
}
 
OutputHandler::getInstance(file_get_contents('header.phtml'), file_get_contents('footer.phtml'));
OutputHandler::setTemplateVar('<!-- Title -->', 'Page title');
OutputHandler::setTemplateVar('<!-- The list -->', <<<ENDOUTPUT
<ul>
	<li>Item 1</li>
	<li>Item 2</li>
</ul>
ENDOUTPUT
);
 
echo '<p>Content, content, content!</p>';
echo '<p>Before the list</p>
	<!-- The list -->
	<p>After the list</p>';

When the script ends the above echo’ed lines will be output within the header and footer templates. The above echo’ed string “<!– The list –>” will be replaced with the unordered list defined with OutputHandler::setTemplateVar() and if the string “<!– Title –>” is found anywhere, including the header or footer templates, it will be replaced with “Page title”.

Set include path for Zend Framework

Thursday, December 13th, 2007

When using Zend Framework, its library must be present in the PHP directive ‘include_path‘, so a common thing to do when using ZF is to set the include_path inside the app and include a directory where the ZF library is supposed to be installed.

Here’s my problem: Instead of hard-coding a path to a directory that may or may not contain the framework, I wanted a simple way to configure include_path without having to override the global setting through php.ini, .htaccess, etc. Naturally I can’t use Zend_Config which I’d otherwise use for configurations.

The directory structure I’m using is based on the structure suggested in the tutorial Getting Started with the Zend Framework by Rob Allen. This means that I have 3 directories in the application root directory, ‘app’ for application files, ‘lib’ for libraries (such as Zend Framework) and ‘pub’ for public files (css, js, images, etc). I could just ship my application with a copy of Zend Framework inside the lib-directory and leave it at that, but I don’t want that since people may already have a copy of the framework on their servers and may not want another copy for storage reasons or just simplicity.

The solution I came up with was to create simple text-files in my applications lib-directory containing paths to include in include_path. My app would then scan this directory and extract the contents of all files matching a given extension before setting include_path using these extracted values. The extension I went with for these simple config-files was ‘.libp’, short for ‘lib-path’.

In my application object constructor, I now have

define('DS', DIRECTORY_SEPARATOR);
define('PS', PATH_SEPARATOR);
define('AR', realpath(dirname(__FILE__) . '/..')); # application root

try
{
	self::_set_include_path();
	if(!@include_once('Zend/Loader.php'))
	{
		throw new Exception('Failed to load Zend Framework. This is a required library and must be installed.');
	}
}
catch(Exception $e)
{
	echo $e->getMessage();
	exit;
}

I think the code is fairly obvious, so I won’t waste time commenting it further.

Later in the same class, I have these methods

private static function _set_include_path ()
{
	$customLibPaths = '';
	$libPath = self::getPath('lib');
	# filter out non-regular files, files starting with a punctuation mark and files not ending with .libp
	foreach (array_filter(scandir($libPath), create_function('$file', 'return (strpos($file, \'.\') === 0 || !is_file(\'' . addslashes($libPath . DS) . '\' . $file) || pathinfo($file, PATHINFO_EXTENSION) !== \'libp\' ? false : true);')) as $file)
	{
		$tmpPath = trim(file_get_contents($libPath . DS . $file));
		if (empty($tmpPath))
		{
			continue;
		}
		if (!is_dir($tmpPath))
		{
			throw new Exception(sprintf('"%s" is not an existing directory, please change %s to contain a real include path. If you don\'t want to set a new include path in this file, you may either delete it or empty it.', $tmpPath, $libPath . DS . $file));
			continue;
		}
		$customLibPaths .= PS . $tmpPath;
	}
	set_include_path(
		AR
		. PS . AR . DS . 'lib'
		. $customLibPaths
		. PS . get_include_path()
		);
}
 
/**
 * Get absolute path to a directory or file by passing any number of path entities (directories/files) relative from the application root
 */
public static function getPath ()
{
	$args = func_get_args(); # Can't be used as a function parameter directly, must be assigned to a variable
	return AR . DS . implode(DS, $args);
}

The lib-directory contains files such as zend-framework.libp, holding a simple string each with the path to the respective libraries. When trying to include files, PHP will now first check the application root, then the lib directory, any custom lib-paths and finally the default include_path.

If I ever want to implement some sort of plugin system for my app, any plugins relying on external libraries may add a file in the lib-directory to configure the include path instead of modifying main application files. I can also use this myself of course if I at a later point want to make use of further third party libraries and allow end-users to easily configure the paths to those libraries.

Trac 0.11 beta coming soon

Saturday, December 8th, 2007

In case you didn’t know, Trac is an open source software project management tool.

According to the Trac roadmap, they’re about to release a beta version of the long-awaited 0.11 release. On that occasion, they ask that people help them make a fool-proof release by downloading and testing the latest revision (trunk) from the subversion repository.

The 0.11 release has been in development for over a year now and it closes more than 400 tickets in their issue tracker. Most issues were defects but there are also several enhancements. Some of the highlights are the integrated web-admin plugin, configurable workflow and easier installation procedure. See the release notes for 0.11 in the Trac wiki for much more info on what’s new. I installed the latest trunk yesterday, and so far it looks really good. Even for a one-man private project, this is a tool that makes a difference.

Blog update

Saturday, December 8th, 2007

I just added OpenID support to this blog and finally allowed people to register and post comments too. Apparently there was a switch in the Wordpress admin, “Anyone can register”, that was turned off.