Last active
August 29, 2015 14:14
-
-
Save RomkeVdMeulen/e0a36ff2c65cc61d448c to your computer and use it in GitHub Desktop.
Baseclass for simple template inheritance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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