Skip to content

Instantly share code, notes, and snippets.

@RomkeVdMeulen
Last active August 29, 2015 14:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RomkeVdMeulen/e0a36ff2c65cc61d448c to your computer and use it in GitHub Desktop.
Save RomkeVdMeulen/e0a36ff2c65cc61d448c to your computer and use it in GitHub Desktop.
Baseclass for simple template inheritance
<?php
/**
* Every renderable can have template files in the directory matching its class. The matching directory
* follows the Zend naming convention, except lowercased to prevent conflicts between OSes.
* E.g. for class `ReqHandler_Home` the path to template "view" would be `reqhandler/home/view.tpl.php`
* This obeys inheritance: e.g. if class `ReqHandler_Home extends ReqHandler_Base`
* and template `reqhandler/base/view.tpl.php` exists, than that template is used by default,
* but can be overriden by `reqhandler/home/view.tpl.php`
*
* Templates can be nested: simply call `$this->render('subtemplate')` from your outer template.
*
* Addionally, the renderable can specify a state, e.g. 'error', by returning it from method `getState()`.
* In this case, the template `view.error.tpl.php` will override the default `view.tpl.php`.
*
* You can also define a constant `OUTPUT_TYPE` to e.g. `json`. In this case,
* `view.json.tpl.php` overrides `view.tpl.php`. This also obeys the state mechanism.
*
* Every occurence of the string `[TID]` in the rendered output of your templates will be replaced
* with the path of the template file. This will help you trace errors in the output
* of multiple nested templates.
*/
abstract class Renderable {
/**
* @const Extension for template files
*/
const EXTENSION = '.tpl.php';
/**
* Identifier for this object
* @return string
*/
public function getName() {
return str_replace('_','-',get_class($this));
}
/**
* Gives all directories associated with the given class
*
* @param string $from_class Class from which to start looking for template dirs, default calling object's class
* @return array:string
*/
public function getDirs($from_class = null) {
$dirs = array();
$class = $from_class === null ? get_class($this) : $from_class;
while ( $class AND $class != __CLASS__ ) {
$dirs[] = $this->getDirForClass($class);
$class = get_parent_class($class);
}
return $dirs;
}
/**
* @var array
*/
private $class_dir_cache = array();
/**
* Find the directory associated with the given class
*
* @param string $class
* @return string|boolean Path to directory, false if not found
*/
protected function getDirForClass( $class ) {
if ( !isset($this->class_dir_cache[$class]) ) {
return $this->class_dir_cache[$class] = $this->findDirForClass($class);
}
return $this->class_dir_cache[$class];
}
/**
* Look across all included paths to find the directory associated with given class
*
* @param string $class
* @return string|boolean Path to directory, false if not found
*/
protected function findDirForClass( $class ) {
foreach ( explode(PATH_SEPARATOR,get_include_path()) as $path ) {
$dir = $path . DIRECTORY_SEPARATOR .
str_replace( '_', DIRECTORY_SEPARATOR, strtolower($class) ) . DIRECTORY_SEPARATOR;
if ( is_dir($dir) ) {
return $dir;
}
}
return false;
}
/**
* Find template file with given name, associated with given class
*
* @param string $template Template name
* @param string $from_class Class from which to start looking for template dirs, defaults to calling object's class
* @return string|null
*/
protected function findTemplateFile( $template, $from_class = null ) {
foreach ( $this->getDirs($from_class) as $dir ) {
if ( file_exists($dir.$template.self::EXTENSION) ) {
return $dir.$template.self::EXTENSION;
}
}
return null;
}
/**
* Renders the given template, optionally with given extra parameters, and returns the resulting text
*
* @throws Exception_TemplateNotFound Thrown if template file not found
* @param string $template Template name
* @param array $vars Optional set of parameters to pass to the template file
* @param string $from_class Class from which to start looking for template dirs, default calling object's class
* @return string
*/
public function render( $template = 'view', array $vars = array(), $from_class = null ) {
foreach ( $vars as $name => $value ) {
global $$name;
$$name = $value;
}
$template_file = $this->findTemplateFile($template,$from_class);
if ( !file_exists($template_file) ) {
throw new Exception_TemplateNotFound('404: template not found - '.
sprintf('The template %s of renderable %s could not be found.',
$template,
get_class($this)
)
);
}
if ( defined('OUTPUT_TYPE') AND $type = trim(OUTPUT_TYPE) ) {
$type_template = $template.'.'.$type;
$type_template_file = $this->findTemplateFile($type_template,$from_class);
if ( file_exists($type_template_file) ) {
$template = $type_template;
$template_file = $type_template_file;
}
}
if ( method_exists($this, 'getState') AND $state = $this->getState() AND trim($state) ) {
$state_template = $template.'.'.$state;
$state_template_file = $this->findTemplateFile($state_template,$from_class);
if ( file_exists($state_template_file) ) {
$template_file = $state_template_file;
}
}
// Use output buffering until rendering is complete
ob_start();
require $template_file;
$output = ob_get_contents();
ob_end_clean();
// Keyword [TID] in templates is replace with template path
return str_replace('[TID]',$template_file,$output);
}
/**
* Render the given template for the parent class of the calling object, optionally with given extra parameters
*
* @throws Exception_TemplateNotFound Thrown if template file not found
* @param string $template Template name
* @param array $vars Optional set of parameters to pass to the template file
* @return string
*/
public function parentRender( $template = 'view', array $vars = array() ) {
return $this->render($template, $vars, get_parent_class(get_class($this)));
}
}
class Exception_TemplateNotFound extends Exception {}
<?php
/**
* To test above code, place this file and Renderable.php in the same directory.
* Then create subdirectory `a/b/` beside these files.
* In this subdirectory, create files `view.tpl.php`, `view.error.tpl.php`
* `view.json.tpl.php`, `view.json.error.tpl.php` and write any content you whish in them.
*
* Use `[TID]` in the templates to see what gets rendered where.
* You can also render subtemplates: `<?php echo $this->render('sub'); ?>
* will render `sub.tpl.php`.
*/
require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'Renderable.php';
class A_B extends Renderable {
public $state = null;
public function getState() { return $this->state; }
}
$c = new A_B;
// Will render a/b/view.tpl.php
echo $c->render('view');
$c->state = 'error';
// Will render a/b/view.error.tpl.php
echo $c->render('view');
$c->state = null;
define('OUTPUT_TYPE','json');
// Will render a/b/view.json.tpl.php
echo $c->render('view');
$c->state = 'error';
// Will render a/b/view.json.error.tpl.php
echo $c->render('view');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment