PHP Templating - php

I'm writing a simple templating layer in PHP but I've got myself a little stuck. Here's how it works at the moment:
Firstly I use fetch_template to load the template contents from the database - this works (and I collect all the templates at startup if you're interested).
I use PHP variables in my template code and in the logic - e.g.:
// PHP:
$name = 'Ross';
// Tpl:
<p>Hello, my name is $name.</p>
I then use output_template (below) to parse through the variables in the template and replace them. Previously I was using template tags with a glorified str_replace template class but it was too inefficient.
/**
* Returns a template after evaluating it
* #param string $template Template contents
* #return string Template output
*/
function output_template($template) {
eval('return "' . $template . '";');
}
My problem, if you haven't already guessed, is that the variables are not declared inside the function - therefore the function can't parse them in $template unless I put them in the global scope - which I'm not sure I want to do. That or have an array of variables as a parameter in the function (which sounds even more tedious but possible).
Does anyone have any solutions other than using the code from the function (it is only a one-liner) in my code, rather than using the function?
Thanks,
Ross
P.s. I know about Smarty and the vast range of templating engines out there - I'm not looking to use them so please don't suggest them. Thanks!

Rather than run through your loop you can use include($template_name).
Or, if you want the content of the output from the template, you can do something like this:
$template_name = 'template.php';
// import the contents into this template
ob_start();
include($template_name);
$content = ob_get_clean();
// do something with $content now ...
And remember, in your template, you can use the often overlooked PHP syntax:
<?php if ($a == 5): ?>
A is equal to 5
<?php endif; ?>
Alternative syntax is available for if, while, for, foreach and switch ... perfect for manipulating the data in your template. See "Alternative syntax for control structures" for more details.

I'd pass an associative array with variables to replace, then extract() them.
Then you could also pass $_GLOBALS to achieve the same result.
function output_template($template, $vars) {
extract($vars);
eval('return "' . $template . '";');
}
Edit: you might also want to consider string subtitution instead of eval, depending on who's allowed to write your templates and on who specifies which template to load. Then there might be a problem with escaping, too...

Also, expanding on davev's comment eval is a bit ugly.
If you can do something like
function inc_scope( $file , $vars )
{
extract($vars);
ob_start();
require($file);
return ob_get_clean();
}
Then you get to use plain-old-php as your templating language, and you don't get any evil-evals, and "extract" + buffering merely limits the visible scope of the php code in the require.

Create file
config.php
index.php
Create folder
inc
template/default/controller/ main files here home.php, login.php, register.php, contact.php, product.php ...
headet.tpl and footer.tpl include home.php file.
main dir /template/default
config.php code here
/* semu design */
// HTTP URL
define('HTTP_SERVER', 'http://localhost/1/');
// HTTPS URL DISABLE
// define('HTTPS_SERVER', 'http://localhost/1/');
// DİZİNLER
define('DIR_INC', 'C:\wamp\www\1/inc/');
define('DIR_TEMLATE', 'C:\wamp\www\1/template/default/');
define('DIR_MODULES', 'C:\wamp\www\1/template/default/module/');
define('DIR_IMAGE', 'C:\wamp\www\1/image/');
define('DIR_CACHE', 'cache'); // [php cache system turkish coder][1]
// DB
define('DB_HOSTNAME', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '123');
define('DB_DATABASE', 'default');
define('DB_PREFIX', '');
index.php code here
<?php
// Version
define('VERSION', '1.0');
// Config file
if (file_exists('config.php')) {
require_once('config.php');
}
// Moduller
require_once(DIR_INC . 'startup.php'); // mysql.php db engine, cache.php, functions.php, mail.php ... vs require_once code
// Cache System
//$sCache = new sCache();
/*$options = array(
'time' => 120,
'buffer' => true,
'load' => false,
//'external'=>array('nocache.php','nocache2.php'), // no cache file
);
$sCache = new sCache($options);*/
// page
$page = isset($_GET['page']) ? trim(strtolower($_GET['page'])) : "home";
$allowedPages = array(
'home' => DIR_TEMPLATE.'controller/home.php',
'login' => DIR_TEMPLATE.'controller/login.php',
'register' => DIR_TEMPLATE.'controller/register.php',
'contact' => DIR_TEMPLATE.'controller/contact.php'
);
include( isset($allowedPages[$page]) ? $allowedPages[$page] : $allowedPages["home"] );
?>
index.php?page=home
index.php?page=login ...
Active class code
<ul>
<li <?php if ( $page == 'home' ) echo 'class="active"'; ?> Home </li>
<li <?php if ( $page == 'login' ) echo 'class="active"'; ?> Login </li>
</ul>
And Token system coming :
index.php?page=home&token=Co54wEHHdvUt4QzjEUyMRQOc9N1bJaeS
Regards.

Related

Psalm: How to handle dedicated view files?

My set-up comprises a lib folder with classes and a view folder with PHP files, that produce output. The views are imported inside a View class similar to this:
class View {
public function render(string $basename, Array $params) : string {
extract($params, EXTR_PREFIX_INVALID, 'v');
ob_start();
include sprintf('%s/views/%s.php', dirname(__DIR__), $basename);
$out = ob_get_contents();
ob_end_clean();
return $out;
}
}
I have basically two problems with Psalm in this situation:
For View::render it reports a UnresolvableInclude. I can even type the $basename with something like
#param "view1"|"view2"|"about" $basename
without effect. The unresolvable include remains.
The extract() puts the content of $params in the local scope, where the view files are included. This allows me to have
<?=escape($foo)?>
“tags” in my view files with $params === ['foo' => 'bar']. However, Psalm doesn’t catch up on this and reports a lot of UndefinedGlobalVariable problems.
My question: How can I tell psalm about the view files and the variables? Or alternatively, how can I re-structure this code so that psalm can test it for me?
There's a demo TemlateChecker plugin in Psalm's repo that seems to do something similar: it looks at the docblock in the view file for the tag like #variablesfrom ClassName::method and makes them available in the template file. Or just properties on $this variable from that method, not sure. It's also mentioned in Psalm docs: Checking non-PHP files.
Alternatively, you could wrap your template into a minimal method/function as technically view is just a function that takes a bunch of variables and returns a string: https://psalm.dev/r/66898ee87f
<?php class HomePageView { // view starts here
/** #param list<string> $sections */
public function render(
string $title,
array $sections
): string { ob_start();
?>
<html>
<head>
<title><?=$title?></title>
</head>
<body>
<?php foreach ($sections as $section): ?>
<section><?=$section?></section>
<?php endforeach; ?>
</body>
</html>
<?php return ob_get_contents(); }} // view ends here ?>
This way any tool that analyzes code (including Psalm, but not limited to) would be able to understand it.

PHP - send result from a function to included file

I want pass result of a function to an included file in that function. let me explain it with an example .
index.php
$siteurl = $_POST['url'];
function validator($siteurl){
global $lang, $siteurl;
......
......
......
return $output;
}
en.php
$lang = array();
$lang['site_url'] = 'Site url is' .$output. '';
$lang['site_url_delete'] = 'delete' .$output. '';
$lang['site_url_edit'] = 'edit ' .$output. '';
well, now i want pass $output from validator function from index.php to fa.php ($lang).
I put this code in fa.php:
$siteurl = $_POST['url'];
$output = validator($siteurl);
It works but it's dirty work because I don't want to call a function (Validator) to each $lang. is there better way to do this?
All files you include in php share the enclosing scope. Think about this like you copy all code from the included file and insert it to the "main" file instead of the include statement.
So, you can write $output = validator($siteurl) in your index.php file, include your lang.php file and directly use the $output variable.
But, however, it is kinda bad practice to use global variables. It may turn your code to a mess after some time. So, be careful and consider to rethink an architecture of your application.
I've been in a wrong way! found the best solution!
solved my problem with function.sprintf
sprintf($lang['site_url'], $output)
$lang['site_url'] = 'Site url is %s';

Is it possible to track a variable back to its extract() function?

I am working with a Drupal theme, and I see a lot of variables which look like were created with extract(). Is it possible to track back, and see where that array is?
I take you are referring to the variables passed to a template file, which effectively are extracted from an array.
The code that does that in Drupal 7 is in theme_render_template().
function theme_render_template($template_file, $variables) {
extract($variables, EXTR_SKIP); // Extract the variables to a local namespace
ob_start(); // Start output buffering
include DRUPAL_ROOT . '/' . $template_file; // Include the template file
return ob_get_clean(); // End buffering and return its contents
}
The function is called from theme(), which executes the following code.
// Render the output using the template file.
$template_file = $info['template'] . $extension;
if (isset($info['path'])) {
$template_file = $info['path'] . '/' . $template_file;
}
$output = $render_function($template_file, $variables);
$render_function by default is set to 'theme_render_template', but its value is set with the following code (in theme()).
// The theme engine may use a different extension and a different renderer.
global $theme_engine;
if (isset($theme_engine)) {
if ($info['type'] != 'module') {
if (function_exists($theme_engine . '_render_template')) {
$render_function = $theme_engine . '_render_template';
}
$extension_function = $theme_engine . '_extension';
if (function_exists($extension_function)) {
$extension = $extension_function();
}
}
}
Just echo the $GLOBALS variable and you might find where it came from if the array was not unset.
Im not familiar with Drupal so this is just a suggestion, but if drupal has a templating structure or if an array is passed from a controller or such then possible that extract is used,
You could use get_defined_vars within your view to get all vars and its possible that there is an array there that you can cross reference with variables you know of that are in the same array or such.
<?php
$vars = get_defined_vars();
print_r($vars);
//or maybe
print_r($this);
?>

CodeIgniter, including code from one view into another?

I have an interesting problem. I currently have a basic template library that renders out a bunch of modules for header and footer templates, then sandwiches a view I specify in between them.
Example:
$this->load->view('header.php', $headerstuff);
$this->load->view($contentView);
$this->load->view('footer.php', $footerstuff);
The problem is that I need to put some javascript (that is specific to each content view) into the header. I have been doing this with a switch statement containing the js inside the template library. But that makes no sense in the mvc model.
Example (of what I've been doing), (in template library, above previous code):
$headerstuff['js'] = '';
switch ($contentView)
{
case 'main':
$headerstuff['js'] = 'JAVASCRIPT INCLUDE CODE 1';
break;
case 'product':
$headerstuff['js'] = 'JAVASCRIPT INCLUDE CODE 2';
break;
}
I can't think of another way to do this though. I would like to (ideally) store the js in a variable inside the content view file, and (somehow) load that into the header view. To be honest though, I don't even think that is possible.
Does anybody have a better way of doing this then my current solution?
Thanks,
Max
I created a helper file to do this for my sites and I think we have a similar MVC template layout:
Asset Helper:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
if ( ! function_exists('css'))
{
function css($array) {
// If the object passed is a string, convert it into an array
if ( is_string($array) ) {
$array = explode(" ", $array);
}
// Add additional CSS Files
if ( isset($array) ) {
foreach ( $array as $i => $file ) {
// If it's not the first one add a tab character.
if ( $i > 0 ) echo "\t";
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"". $file ."\">\n";
}
}
}
}
if ( ! function_exists('js'))
{
function js($array) {
// If the object passed is a string, convert it into an array
if ( is_string($array) ) {
$array = explode(" ", $array);
}
// Add additional JavaScript Files
if ( isset($array) ) {
foreach ( $array as $i => $file ) {
// If it's not the first one add a tab character.
if ( $i > 0 ) echo "\t";
echo "<script src=\"". $file ."\"></script>\n";
}
}
}
}
This does one of two things. I will allow you to add files in the controller file which is nice. I do this by creating a data object:
$data['css'] = array('/path/to/styles.css', '/path/to/otherstuff.css');
$data['js'] = array('/path/to/javascript.js');
Then in your header include do the following:
<?
$defaultCSS = array("/assets/css/global.css");
$css = (isset($css)) ? array_merge($defaultCSS, $css) : $defaultCSS;
css($css);
$defaultJS = array("/assets/js/global.js");
$js = (isset($js)) ? array_merge($defaultJS, $js) : $defaultJS;
js($js);
?>
I'm settings some defaults that will load on each page and then I can add in different files based on which controller I'm loading.
Hope this helps.
How about having a scripts controller which acts as a small proxy:
/**
* A basic controller which allows for the display of basic views.
*/
class Scripts extends CI_Controller
{
public function _remap( $meth, array $params )
{
// ensure that parent directory contents can't be revealed.
if( !$meth || strpos( $meth, '.' ) !== FALSE ) show_404();
foreach( $params as $arg )
// I put all css in a folder called css all js goes into
// a folder called js. It can be overly simple, but it works
// (if you need these files to be php files, just add '.php'
// to the end of the FILE's name. No need to worry about that
// here, CodeIgniter's got your back...)
$this->load->view( "$meth/${params}.$meth" );
}
}
How to use:
For each controller (or, you could easily modify this to be for every method), have an appropriate js file in your views/js folder. Name each after the respective controller. Then, point to each of them from the view:
Then, in the header view:
<script type="text/javascript" src="
<?php
// honestly, I normally use a helper function here, but this is the
// short... short version.
echo base_url() . '/scripts/js/' . load_class('Router')->fetch_class();
// You can also replace the controller name with the view name by using the
// variable $_ci_view, you can get the full path with $_ci_path
?>" ></script>
Best part? Your controller is agnostic about the contents of the view. The view is more or less agnostic of the controller (it is a variable populated ahead of time from an external source and just... there). In fact, the only things which "need to know" are the JS/CSS files and they were on a case by case configuration anyway.

Using a :default for file names on include templates in SMARTY 3

Although I don't think the question was as good as it could be, let me try to explain better here.
I have a site using SMARTY 3 as the template system. I have a template structure similar to the below one:
/templates/place1/inner_a.tpl
/templates/place1/inner_b.tpl
/templates/place2/inner_b.tpl
/templates/place2/inner_c.tpl
/templates/default/inner_a.tpl
/templates/default/inner_b.tpl
/templates/default/inner_c.tpl
These are getting included on the parent template using
{include file="{$temp_folder}/{$inner_template}"}
So far great. What I wanted to do is having a default for, in the case that the file {$temp_folder}/{$inner_template} does not exists, it uses the equivalent file at default/{$inner_template}.
i.e. If I do {include file="place1/inner_c.tpl"}, since that file does not exists it in fact includes "default/inner_c.tpl"
Is it possible?
You'll have to do it in php, smarty doesn't have a way to check if a file exists.
You could write your own template handler too.
<?php
// put this function somewhere in your application
function make_template ($resource_type, $resource_name, &$template_source, &$template_timestamp,
&$smarty_obj)
{
if( $resource_type == 'file' ) {
if ( ! is_readable ( $resource_name )) {
// create the template file, return contents.
$template_source = "This is a new template.";
require_once SMARTY_CORE_DIR . 'core.write_file.php';
smarty_core_write_file( array( 'filename'=>$smarty_obj->template_dir . DIRECTORY_SEPARATOR . $resource_name, 'contents'=>$template_source ), $smarty_obj );
return true;
}
} else {
// not a file
return false;
}
}
// set the default handler
$smarty->default_template_handler_func = 'make_template';
?>

Categories