A PHP Library class
Yet another frameworky solution
This page describes an effort to create website modules: parts of a website (say a guestbook, or a graphical representation of some tree structure) that you can slot in and out with a single php command. The idea is very similar to that of packages in java: if you need 3D capabilities somewhere in your program you just include a 3D package at the top of your file.
This is certainly possible using php alone, but when you include css and javascript it gets a bit more complicated. The solution used here is to define a library item as a set of php, java and css files, make a list of those files, and then make a Library object that adds the right files to the html page whenever you load a library item. This means we need the following components:
- A Library class
- A Library Item class
- A list of library items available to the library class
Last but not least, the implementation relies on the use of a templating system and templates having a few extra lines to include the library files.
Two classes and an include
Here's what the Library class looks like:
<?php
/*
* Represent a library to load php/css/js combos from.
*/
class Library
{
/**
* Assignes a template for this library to use, must be called at least once
* prior to loading a library item.
*/
public static function setTemplate(Template $tpl)
{
if (!self::$initialised) {
// 'LIB' is a constant pointing to the directory library items are
// stored in
self::$lib = include(LIB."inc.items.php");
self::$initialised = true;
}
self::$tpl = $tpl;
}
/*
* Loads a library item.
*/
public static function load($name)
{
// Check for a template. This isn't very pretty. An alternative is to not
// make everything static, and require the template to be passed in the constructor.
if (!isset(self::$tpl))
throw new Exception("No template set for library");
if (!isset(self::$lib[$name])) return false;
if (in_array($name, self::$loaded)) return false;
$item = self::$lib[$name];
$item->load(self::$tpl);
self::$loaded[] = $name;
}
private static $lib = array(); // library data (items)
private static $loaded = array(); // names of loaded libraries
private static $tpl;
private static $initialised = false;
}
?>
Creating the initial list of (not loaded!) library items is done by including the file inc.items.php (displayed below), which returns an array of LibraryItem objects. That's another sweet php trick: an included file can have a return value.
The LibraryItem class is displayed below:
<?php
class LibraryItem
{
public $name, $path;
public $php, $css, $jsh, $jsb;
public function LibraryItem($name, $path, $php=false, $css=false, $jsh=false, $jsb=false)
{
$this->name = $name;
$this->path = $path;
if ($php !== false)
if (is_array($php)) {$this->php = $php;} else {$this->php = array($php);}
if ($css !== false)
if (is_array($css)) {$this->css = $css;} else {$this->css = array($css);}
if ($jsh !== false)
if (is_array($jsh)) {$this->jsh = $jsh;} else {$this->jsh = array($jsh);}
if ($jsb !== false)
if (is_array($jsb)) {$this->jsb = $jsb;} else {$this->jsb = array($jsb);}
}
public function load(Template $tpl=null)
{
if (isset($this->php))
foreach($this->php as $php) {require_once($this->path.$php);}
if (!is_null($tpl)) {
if (isset($this->css))
foreach($this->css as $css) {$tpl->addCSS($this->path.$css);}
if (isset($this->jsh))
foreach($this->jsh as $jsh) {$tpl->addJSH($this->path.$jsh);}
if (isset($this->jsb))
foreach($this->jsb as $jsb) {$tpl->addJSB($this->path.$jsb);}
}
}
}
?>
Any time the load() method is called by the Library class the item loads any php classes associated with this item and adds any css or js to the template. The adding of php files is done using the require_once() function. This is similar to the include() function but throws an exception if the file can't be found and never includes the same file more than once.
The following file is repsonsible for creating the list of Library Items available for loading. It is referenced to as inc.items.php by the Library class.
<?php
$items = array();
// Tree
// Description: A Tree data structure and a treeview object with javascript to
// expand or collapse branches.
$items['Tree'] = new LibraryItem(
'Tree', // item name
LIB.'Tree/', // item folder
array('class.TreeNode.php', 'class.TreeView.php'), // php file(s)
'TreeView.css', // css file(s)
'head.js', // javascript for in the head
'body.js' // javascript for in the body
);
// ImageEditor
// Description: Creates an ImageEditor class that allows a user to [RIGHT NOW YOU CAN ONLY BROWSE]
$items['ImageEditor'] = new LibraryItem(
'ImageEditor',
LIB.'ImageEditor/',
array('class.ImageHandler.php', 'class.ImageEditor.php'),
'image_editor.css',
false,
'body.js'
);
// Page
// Description: Everything needed to access / edit pages in the cms
$items['Page'] = new LibraryItem(
'Page',
LIB.'Page/',
array(
'class.PageNode.php',
'class.Page.php',
'class.PageVersion.php',
'inc.Initialise.php'
),
false,
false,
false
);
return($items);
?>
As you can see, this one's a bit of a mess. If you like you could use a database or xml or some better looking solution. For now though, this works just fine.
Templating revisited
The LibraryItem class shown above uses methods like addCSS() on the template, this requires some modification of the original template class.
<?php
/**
* A simple templating class using PHP as/instead of a 'native scripting language'
* Based on an article by Brian Lozier (brian@massassi.net)
*/
class Template
{
private $vars = array(); // Holds all the template variables
private $file; // The template file loaded
private $css = array();
private $jsb = array();
private $jsh = array();
private $cssText = "";
/*
* Constructor
*/
public function __construct($file = null)
{
$this->file = $file;
}
/*
* Set a template variable.
*/
public function set($name, $value)
{
$this->vars[$name] = is_object($value) ? $value->fetch() : $value;
}
/*
* Open, parse, and return the template file.
* @param $file string the template file name
*/
public function fetch($file = null)
{
if(!$file) $file = $this->file;
extract($this->vars); // Extract the vars to local namespace
extract($this->compileScriptFiles()); // Same but now for script files
ob_start(); // Start output buffering
include($file); // Include the file
return ob_get_clean(); // Return the contents of the buffer
}
private function compileScriptFiles()
{
$cssCode = '';
$jshCode = '';
$jsbCode = '';
$cssText = '';
if (!empty($this->css)) {
foreach($this->css as $file) {
$cssCode .= "\t<link href=\"".$file."\" rel=\"stylesheet\" type=\"Code/css\" />\n";
}
}
if (!empty($this->jsh)) {
foreach($this->jsh as $file) {
$jshCode .= "\t<script src=\"".$file."\" type=\"Code/javascript\"></script>\n";
}
}
if (!empty($this->jsb)) {
foreach($this->jsb as $file) {
$jsbCode .= "\t<script src=\"".$file."\" type=\"Code/javascript\"></script>\n";
}
}
if (!empty($this->cssText)) {
$cssText = '<style type="text/css">'."\n".$this->cssText.'</style>';
}
$out = array();
$out['cssFile'] = $cssCode;
$out['jshFile'] = $jshCode;
$out['jsbFile'] = $jsbCode;
$out['cssText'] = $cssText;
return $out;
}
/*
* Add a css file to this template
*/
public function addCSS($file)
{
if (!in_array($file, $this->css)) $this->css[] = $file;
}
/*
* Add a javascript file to this template, to be loaded when the body loads
*/
public function addJSB($file)
{
if (!in_array($file, $this->jsb)) $this->jsb[] = $file;
}
/*
* Add a javascript file to this template, to be loaded before the page loads
*/
public function addJSH($file)
{
if (!in_array($file, $this->jsh)) $this->jsh[] = $file;
}
/*
* Add a line of css to place in the head of the page
*/
public function addInternalCSS($css)
{
if (substr($css, -2) != "\n") $css .= "\n";
$this->cssText .= $css;
}
}
?>
Four new public methods were added. Although these methods (with the exception of addInternalCSS($css)) are intended for use by the LibraryItem class, you can also use them if you want to add some scripting not present in the default template. Here's what they do:
- addCSS($file)
- Adds a css file the the page head
- addJSH($file)
- Adds a javascript file the the page head
- addJSB($file)
- Adds a javascript file the the page body. This script will only be executed when the page is fully loaded and is the place where you add event handlers to objects etc.
- addInternalCSS($css)
- This method was added as an afterthought, and lets you add a single line of css to the page head. This can be useful if you need some slight modification of the css on a particular page. Purists will probably kill you for this
At this point, a list of css & js files is available to the template. Now all we need is to adapt the template to include them. Here's one way of doing it:
<!-- Doctype goes here -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="title" content="<?php echo $title?>" />
<title><?php echo $title?></title>
<link href="yourDefault.css" rel="stylesheet" type="text/css" />
<?php echo $cssText;?>
<?php echo $cssFile;?>
<?php echo $jshFile;?>
</head>
<body>
<div id="content">
<h1>Hollow world</h1>
<h6>A goth-nerd opera in seven acts</h6>
<p>It was a dark and stormy night. Blah blah blah rain pain miasma etc.</p>
</div>
<?php echo $jsbFile; ?>
</body>
</html>
So that's how I do it. Not quite sure if it's good. There's lots of frameworks out there that include something similar to this. If you're not using templating but doing every page individually you can get the same functionality by manualy including the necessary script files on each page. Another thing to consider is how script files are loaded. Firebug shows you this tends to happen sequentially. This means you can avoid some overhead by packing all your css into a single file. Numerous applications exist to do this, usually removing all comments and whitespace in the process, resulting in a smaller file.
At the very worst, you can remove all the js/css parts (thereby removing the need for templates) and still use it as a php package system. I like doing this because it means I can use a steady set of core functions and then add the extra classes where I need them. So... When it comes to creating a modular approach to building webpages, this is as close as I've been able to get. Have fun!
Jul 5th, 2008
Comments
Michael wrote:
...aaaand moving right back again
Feb 1st, 2010
Michael wrote:
It might interest you to know that I'm moving away from this approach (this site still uses it), preferring to use javascript only for a few cosmetic touches and keeping css, js and php clearly separated (and without knowledge of each other's existence). The problem of keeping your php classes apart will hopefully be solved with the release of php 5.3 with namespace support.
Namespaces!
I can't remember any php functionality as eagerly anticipated as this one :)
Dec 13th, 2008
If you wish to add code to your comment you can use code tags, like this: <code class="php">yourCodeHere</code>.
Quite a large number of languages are supported, although I can't guarantee it'll be pretty. Inside the code tags you can use any characters except for the string "</code>".