PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/Template.class.php

https://bitbucket.org/dlsbdaniel/fpconvert
PHP | 445 lines | 186 code | 29 blank | 230 comment | 43 complexity | 53ba204ca7e316864a5774919e0cd66d MD5 | raw file
  1. <?php
  2. /**
  3. * Template Management for PHP5
  4. *
  5. * The Template engine allows to keep the HTML code in some external files
  6. * which are completely free of PHP code. This way, it's possible keep logical
  7. * programmin (PHP code) away from visual structure (HTML or XML, CSS, etc).
  8. *
  9. * If you are familiar with PHP template concept, this class includes these
  10. * features: object support, auto-detect blocks, auto-clean children blocks,
  11. * warning when user call for a non-existent block, warning when a mal-formed
  12. * block is detected, warning when user sets a non existant variable, and other
  13. * minor features.
  14. *
  15. * @author Rael G.C. (rael.gc@gmail.com)
  16. * @version 1.92
  17. */
  18. class Template {
  19. /**
  20. * A list of existent document variables.
  21. *
  22. * @var array
  23. */
  24. private $vars = array();
  25. /**
  26. * A hash with vars and values setted by the user.
  27. *
  28. * @var array
  29. */
  30. private $values = array();
  31. /**
  32. * A hash of existent object properties variables in the document.
  33. *
  34. * @var array
  35. */
  36. private $properties = array();
  37. /**
  38. * A hash of the object instances setted by the user.
  39. *
  40. * @var array
  41. */
  42. private $instances = array();
  43. /**
  44. * A list of all automatic recognized blocks.
  45. *
  46. * @var array
  47. */
  48. private $blocks = array();
  49. /**
  50. * A list of all blocks that contains at least a "child" block.
  51. *
  52. * @var array
  53. */
  54. private $parents = array();
  55. /**
  56. * Describes the replace method for blocks. See the Template::setFile()
  57. * method for more details.
  58. *
  59. * @var boolean
  60. */
  61. private $accurate;
  62. /**
  63. * Regular expression to find var and block names.
  64. * Only alfa-numeric chars and the underscore char are allowed.
  65. *
  66. * @var string
  67. */
  68. private static $REG_NAME = "([[:alnum:]]|_)+";
  69. /**
  70. * Creates a new template, using $filename as main file.
  71. *
  72. * When the parameter $accurate is true, blocks will be replaced perfectly
  73. * (in the parse time), e.g., removing all \t (tab) characters, making the
  74. * final document an accurate version. This will impact (a lot) the
  75. * performance. Usefull for files using the &lt;pre&gt; or &lt;code&gt; tags.
  76. *
  77. * @param string $filename file path of the file to be loaded
  78. * @param booelan $accurate true for accurate block parsing
  79. */
  80. public function __construct($filename, $accurate = false){
  81. $this->accurate = $accurate;
  82. $this->loadfile(".", $filename);
  83. }
  84. /**
  85. * Put the content of $filename in the template variable identified by $varname
  86. *
  87. * @param string $varname existing template var
  88. * @param string $filename file to be loaded
  89. */
  90. public function addFile($varname, $filename){
  91. if(!$this->exists($varname)) throw new InvalidArgumentException("addFile: var $varname does not exist");
  92. $this->loadfile($varname, $filename);
  93. }
  94. /**
  95. * Do not use. Properties setter method
  96. *
  97. * @param string $varname template var name
  98. * @param mixed $value template var value
  99. */
  100. public function __set($varname, $value){
  101. if(!$this->exists($varname)) throw new RuntimeException("var $varname does not exist");
  102. $stringValue = $value;
  103. if(is_object($value)){
  104. $this->instances[$varname] = $value;
  105. if(!array_key_exists($varname, $this->properties)) $this->properties[$varname] = array();
  106. if(method_exists($value, "__toString")) $stringValue = $value->__toString();
  107. else $stringValue = "Object";
  108. }
  109. $this->setValue($varname, $stringValue);
  110. return $value;
  111. }
  112. /**
  113. * Do not use. Properties getter method.
  114. *
  115. * @param string $varname template var name
  116. */
  117. public function __get($varname){
  118. if (isset($this->values["{".$varname."}"])) return $this->values["{".$varname."}"];
  119. throw new RuntimeException("var $varname does not exist");
  120. }
  121. /**
  122. * Check if a template var exists.
  123. *
  124. * This method returns true if the template var exists. Otherwise, false.
  125. *
  126. * @param string $varname template var name
  127. */
  128. public function exists($varname){
  129. return in_array($varname, $this->vars);
  130. }
  131. /**
  132. * Loads a file identified by $filename.
  133. *
  134. * The file will be loaded and the file's contents will be assigned as the
  135. * variable's value.
  136. * Additionally, this method call Template::recognize() that identifies
  137. * all blocks and variables automatically.
  138. *
  139. * @param string $varname contains the name of a variable to load
  140. * @param string $filename file name to be loaded
  141. *
  142. * @return void
  143. */
  144. private function loadfile($varname, $filename) {
  145. if (!file_exists($filename)) throw new InvalidArgumentException("file $filename does not exist");
  146. // If it's PHP file, parse it
  147. if($this->isPHP($filename)){
  148. ob_start();
  149. require $filename;
  150. $str = ob_get_contents();
  151. ob_end_clean();
  152. $this->setValue($varname, $str);
  153. } else {
  154. // Reading file and hiding comments
  155. $str = preg_replace("/<!---.*?--->/smi", "", file_get_contents($filename));
  156. $this->setValue($varname, $str);
  157. $blocks = $this->recognize($str, $varname);
  158. if (empty($str)) throw new InvalidArgumentException("file $filename is empty");
  159. $this->createBlocks($blocks);
  160. }
  161. }
  162. /**
  163. * Check if file is a .php
  164. */
  165. private function isPHP($filename){
  166. foreach(array('.php', '.php5', '.cgi') as $php){
  167. if(0 == strcasecmp($php, substr($filename, strripos($filename, $php)))) return true;
  168. }
  169. return false;
  170. }
  171. /**
  172. * Identify all blocks and variables automatically and return them.
  173. *
  174. * All variables and blocks are already identified at the moment when
  175. * user calls Template::setFile(). This method calls Template::identifyVars()
  176. * and Template::identifyBlocks() methods to do the job.
  177. *
  178. * @param string $content file content
  179. * @param string $varname contains the variable name of the file
  180. *
  181. * @return array an array where the key is the block name and the value is an
  182. * array with the children block names.
  183. */
  184. private function recognize(&$content, $varname){
  185. $blocks = array();
  186. $queued_blocks = array();
  187. foreach (explode("\n", $content) as $line) {
  188. if (strpos($line, "{")!==false) $this->identifyVars($line);
  189. if (strpos($line, "<!--")!==false) $this->identifyBlocks($line, $varname, $queued_blocks, $blocks);
  190. }
  191. return $blocks;
  192. }
  193. /**
  194. * Identify all user defined blocks automatically.
  195. *
  196. * @param string $line contains one line of the content file
  197. * @param string $varname contains the filename variable identifier
  198. * @param string $queued_blocks contains a list of the current queued blocks
  199. * @param string $blocks contains a list of all identified blocks in the current file
  200. *
  201. * @return void
  202. */
  203. private function identifyBlocks(&$line, $varname, &$queued_blocks, &$blocks){
  204. $reg = "/<!--\s*BEGIN\s+(".self::$REG_NAME.")\s*-->/sm";
  205. preg_match($reg, $line, $m);
  206. if (1==preg_match($reg, $line, $m)){
  207. if (0==sizeof($queued_blocks)) $parent = $varname;
  208. else $parent = end($queued_blocks);
  209. if (!isset($blocks[$parent])){
  210. $blocks[$parent] = array();
  211. }
  212. $blocks[$parent][] = $m[1];
  213. $queued_blocks[] = $m[1];
  214. }
  215. $reg = "/<!--\s*END\s+(".self::$REG_NAME.")\s*-->/sm";
  216. if (1==preg_match($reg, $line)) array_pop($queued_blocks);
  217. }
  218. /**
  219. * Identifies all variables defined in the document.
  220. *
  221. * @param string $line contains one line of the content file
  222. */
  223. private function identifyVars(&$line){
  224. $r = preg_match_all("/{(".self::$REG_NAME.")((\-\>(".self::$REG_NAME."))*)?}/", $line, $m);
  225. if ($r){
  226. for($i=0; $i<$r; $i++){
  227. // Object var detected
  228. if($m[3][$i] && (!array_key_exists($m[1][$i], $this->properties) || !in_array($m[3][$i], $this->properties[$m[1][$i]]))){
  229. $this->properties[$m[1][$i]][] = $m[3][$i];
  230. }
  231. if(!in_array($m[1][$i], $this->vars)) $this->vars[] = $m[1][$i];
  232. }
  233. }
  234. }
  235. /**
  236. * Create all identified blocks given by Template::identifyBlocks().
  237. *
  238. * @param array $blocks contains all identified block names
  239. * @return void
  240. */
  241. private function createBlocks(&$blocks) {
  242. $this->parents = array_merge($this->parents, $blocks);
  243. foreach($blocks as $parent => $block){
  244. foreach($block as $chield){
  245. if(in_array($chield, $this->blocks)) throw new UnexpectedValueException("duplicated block: $chield");
  246. $this->blocks[] = $chield;
  247. $this->setBlock($parent, $chield);
  248. }
  249. }
  250. }
  251. /**
  252. * A variable $parent may contain a variable block defined by:
  253. * &lt;!-- BEGIN $varname --&gt; content &lt;!-- END $varname --&gt;.
  254. *
  255. * This method removes that block from $parent and replaces it with a variable
  256. * reference named $block. The block is inserted into the varKeys and varValues
  257. * hashes.
  258. * Blocks may be nested.
  259. *
  260. * @param string $parent contains the name of the parent variable
  261. * @param string $block contains the name of the block to be replaced
  262. * @return void
  263. */
  264. private function setBlock($parent, $block) {
  265. $name = "B_".$block;
  266. $str = $this->getVar($parent);
  267. if($this->accurate){
  268. $str = str_replace("\r\n", "\n", $str);
  269. $reg = "/\t*<!--\s*BEGIN\s+$block\s+-->\n*(\s*.*?\n?)\t*<!--\s+END\s+$block\s*-->\n?/sm";
  270. }
  271. else $reg = "/<!--\s*BEGIN\s+$block\s+-->\s*(\s*.*?\s*)<!--\s+END\s+$block\s*-->\s*/sm";
  272. if(1!==preg_match($reg, $str, $m)) throw new UnexpectedValueException("mal-formed block $block");
  273. $this->setValue($name, '');
  274. $this->setValue($block, $m[1]);
  275. $this->setValue($parent, preg_replace($reg, "{".$name."}", $str));
  276. }
  277. /**
  278. * Internal setValue() method.
  279. *
  280. * The main difference between this and Template::__set() method is this
  281. * method cannot be called by the user, and can be called using variables or
  282. * blocks as parameters.
  283. *
  284. * @param string $varname constains a varname
  285. * @param string $value constains the new value for the variable
  286. * @return void
  287. */
  288. private function setValue($varname, $value) {
  289. $this->values["{".$varname."}"] = $value;
  290. }
  291. /**
  292. * Returns the value of the variable identified by $varname.
  293. *
  294. * @param string $varname the name of the variable to get the value of
  295. * @return string the value of the variable passed as argument
  296. */
  297. private function getVar($varname) {
  298. return $this->values['{'.$varname.'}'];
  299. }
  300. /**
  301. * Clear the value of a variable.
  302. *
  303. * Alias for $this->setValue($varname, "");
  304. *
  305. * @param string $varname var name to be cleaned
  306. * @return void
  307. */
  308. public function clear($varname) {
  309. $this->setValue($varname, "");
  310. }
  311. /**
  312. * Fill in all the variables contained within the variable named
  313. * $varname. The resulting value is returned as the function result and the
  314. * original value of the variable varname is not changed. The resulting string
  315. * is not "finished", that is, the unresolved variable name policy has not been
  316. * applied yet.
  317. *
  318. * @param string $varname the name of the variable within which variables are to be substituted
  319. * @return string the value of the variable $varname with all variables substituted.
  320. */
  321. private function subst($varname) {
  322. $s = $this->getVar($varname);
  323. // Common variables replacement
  324. $s = str_replace(array_keys($this->values), $this->values, $s);
  325. // Object variables replacement
  326. foreach($this->instances as $var => $instance){
  327. foreach($this->properties[$var] as $properties){
  328. if(false!==strpos($s, "{".$var.$properties."}")){
  329. $pointer = $instance;
  330. $property = explode("->", $properties);
  331. for($i = 1; $i < sizeof($property); $i++){
  332. if(!is_null($pointer)){
  333. $obj = strtolower(str_replace('_', '', $property[$i]));
  334. // Property acessor
  335. if(property_exists($pointer, $obj)){
  336. $pointer = $pointer->$obj;
  337. // Non boolean accessor
  338. } elseif(method_exists($pointer, "get$obj")){
  339. $pointer = $pointer->{"get$obj"}();
  340. // Boolean accessor
  341. } elseif(method_exists($pointer, "is$obj")){
  342. $pointer = $pointer->{"is$obj"}();
  343. // Magic __get accessor
  344. } elseif(method_exists($pointer, "__get")){
  345. $pointer = $pointer->__get($property[$i]);
  346. // Accessor dot not exists: throw Exception
  347. } else {
  348. $className = $property[$i-1] ? $property[$i-1] : get_class($instance);
  349. $class = is_null($pointer) ? "NULL" : get_class($pointer);
  350. throw new BadMethodCallException("no accessor method in class ".$class." for ".$className."->".$property[$i]);
  351. }
  352. }
  353. }
  354. // Checking if final value is an object
  355. if(is_object($pointer)){
  356. if(method_exists($pointer, "__toString")){
  357. $pointer = $pointer->__toString();
  358. } else {
  359. $pointer = "Object";
  360. }
  361. }
  362. // Replace
  363. $s = str_replace("{".$var.$properties."}", $pointer, $s);
  364. }
  365. }
  366. }
  367. return $s; }
  368. /**
  369. * Clear all child blocks of a given block.
  370. *
  371. * @param string $block a block with chield blocks.
  372. */
  373. private function clearBlocks($block) {
  374. if (isset($this->parents[$block])){
  375. $chields = $this->parents[$block];
  376. foreach($chields as $chield){
  377. $this->clear("B_".$chield);
  378. }
  379. }
  380. }
  381. /**
  382. * Show a block.
  383. *
  384. * This method must be called when a block must be showed.
  385. * Otherwise, the block will not appear in the resultant
  386. * content.
  387. *
  388. * If the parameter $append is true, the block
  389. * content will be appended with the previous content.
  390. *
  391. * @param string $block the block name to be parsed
  392. * @param boolean $append true if the content must be appended
  393. */
  394. public function block($block, $append = true) {
  395. if(!in_array($block, $this->blocks)) throw new InvalidArgumentException("block $block does not exist");
  396. if ($append) $this->setValue("B_".$block, $this->getVar("B_".$block) . $this->subst($block));
  397. else $this->setValue("B_".$block, $this->subst($block));
  398. $this->clearBlocks($block);
  399. }
  400. /**
  401. * Returns the final content
  402. *
  403. * @return string
  404. */
  405. public function parse() {
  406. // After subst, remove empty vars
  407. return preg_replace("/{(".self::$REG_NAME.")((\-\>(".self::$REG_NAME."))*)?}/", "", $this->subst("."));
  408. }
  409. /**
  410. * Print the final content.
  411. */
  412. public function show() {
  413. echo $this->parse();
  414. }
  415. }
  416. ?>