PHP Looping Template Engine - From Scratch - php

For a group project I am trying to create a template engine for PHP for the people less experienced with the language can use tags like {name} in their HTML and the PHP will replace that tag with a predefined variable from an array. As well as supporting loops.
This is well beyond the expectations of the project, but as I have experience with PHP I thought it would be a good challenge to keep me busy!
My main questions are, how do I do the loop part of the parser and is this the best way to implement such a system. Before you just recommend an existing template system, I would prefer to create it myself for experience and because everything in our project has to be our own.
At the moment the basic parsing is done with regex and preg_replace_callback, it checks if $data[name] exists and if it does replaces it.
I have tried to do the loop a variety of different ways but am not sure if I am on the correct track!
An example if the data the parsing engine was given is:
Array
(
[title] => The Title
[subtitle] => Subtitle
[footer] => Foot
[people] => Array
(
[0] => Array
(
[name] => Steve
[surname] => Johnson
)
[1] => Array
(
[name] => James
[surname] => Johnson
)
[2] => Array
(
[name] => josh
[surname] => Smith
)
)
[page] => Home
)
And the page it was parsing was something like:
<html>
<title>{title}</title>
<body>
<h1>{subtitle}</h1>
{LOOP:people}
<b>{name}</b> {surname}<br />
{ENDLOOP:people}
<br /><br />
<i>{footer}</i>
</body>
</html>
It would produce something similar to:
<html>
<title>The Title</title>
<body>
<h1>Subtitle</h1>
<b>Steve</b> Johnson<br />
<b>James</b> Johnson<br />
<b>Josh</b> Smith<br />
<br /><br />
<i>Foot</i>
</body>
</html>
Your time is incredibly appreciated with this!
Many thanks,
P.s. I completely disagree that because I am looking to create something similar to what already exists for experience, my well formatted and easy to understand question gets down voted.
P.p.s It seems there is a massive spread of opinions for this topic, please don't down vote people because they have a different opinion to you. Everyone is entitled to their own!

A simple approach is to convert the template into PHP and run it.
$template = preg_replace('~\{(\w+)\}~', '<?php $this->showVariable(\'$1\'); ?>', $template);
$template = preg_replace('~\{LOOP:(\w+)\}~', '<?php foreach ($this->data[\'$1\'] as $ELEMENT): $this->wrap($ELEMENT); ?>', $template);
$template = preg_replace('~\{ENDLOOP:(\w+)\}~', '<?php $this->unwrap(); endforeach; ?>', $template);
For example, this converts the template tags to embedded PHP tags.
You'll see that I made references to $this->showVariable(), $this->data, $this->wrap() and $this->unwrap(). That's what I'm going to implement.
The showVariable function shows the variable's content. wrap and unwrap is called on each iteration to provide closure.
Here is my implementation:
class TemplateEngine {
function showVariable($name) {
if (isset($this->data[$name])) {
echo $this->data[$name];
} else {
echo '{' . $name . '}';
}
}
function wrap($element) {
$this->stack[] = $this->data;
foreach ($element as $k => $v) {
$this->data[$k] = $v;
}
}
function unwrap() {
$this->data = array_pop($this->stack);
}
function run() {
ob_start ();
eval (func_get_arg(0));
return ob_get_clean();
}
function process($template, $data) {
$this->data = $data;
$this->stack = array();
$template = str_replace('<', '<?php echo \'<\'; ?>', $template);
$template = preg_replace('~\{(\w+)\}~', '<?php $this->showVariable(\'$1\'); ?>', $template);
$template = preg_replace('~\{LOOP:(\w+)\}~', '<?php foreach ($this->data[\'$1\'] as $ELEMENT): $this->wrap($ELEMENT); ?>', $template);
$template = preg_replace('~\{ENDLOOP:(\w+)\}~', '<?php $this->unwrap(); endforeach; ?>', $template);
$template = '?>' . $template;
return $this->run($template);
}
}
In wrap() and unwrap() function, I use a stack to keep track of current state of variables. Precisely, wrap($ELEMENT) saves the current data to the stack, and then add the variables inside $ELEMENT into current data, and unwrap() restores the data from the stack back.
For extra security, I added this extra bit to replace < with PHP echos:
$template = str_replace('<', '<?php echo \'<\'; ?>', $template);
Basically to prevent any kind of injecting PHP codes directly, either <?, <%, or <script language="php">.
Usage is something like this:
$engine = new TemplateEngine();
echo $engine->process($template, $data);
This isn't the best method, but it is one way it could be done.

Ok firstly let me explain something tell you that PHP IS A TEMPLATE PARSER.
Doing what your doing is like creating a template parser from a template parser, pointless and to be quite frank it iterates me that template parser's such as smarty have become so well at a pointless task.
What you should be doing is creating a template helper, not a parser as there redundant, in programming terms a template file is referred to as a view and one of the reasons they was given a particular name is that people would know there separate from Models, Domain Logic etc
What you should be doing is finding a way to encapsulate all your view data within your views themselves.
An example of this is using 2 classes
Template
TemplateScope
The functionality of the template class is for the Domain Logic to set data to the view and process it.
Here's a quick example:
class Template
{
private $_tpl_data = array();
public function __set($key,$data)
{
$this->_tpl_data[$key] = $data;
}
public function display($template,$display = true)
{
$Scope = new TemplateScope($template,$this->_tpl_data); //Inject into the view
if($display === true)
{
$Scope->Display();
exit;
}
return $Scope;
}
}
This is extreamly basic stuff that you could extend, oko so about the Scope, This is basically a class where your views compile within the interpreter, this will allow you to have access to methods within the TemplateScope class but not outside the scope class, i.e the name.
class TemplateScope
{
private $__data = array();
private $compiled;
public function __construct($template,$data)
{
$this->__data = $data;
if(file_exists($template))
{
ob_start();
require_once $template;
$this->compiled = ob_get_contents();
ob_end_clean();
}
}
public function __get($key)
{
return isset($this->__data[$key]) ? $this->__data[$key] : null;
}
public function _Display()
{
if($this->compiled !== null)
{
return $this->compiled;
}
}
public function bold($string)
{
return sprintf("<strong>%s</strong>",$string);
}
public function _include($file)
{
require_once $file; // :)
}
}
This is only basic and not working but the concept is there, Heres a usage example:
$Template = new Template();
$Template->number = 1;
$Template->strings = "Hello World";
$Template->arrays = array(1,2,3,4)
$Template->resource = mysql_query("SELECT 1");
$Template->objects = new stdClass();
$Template->objects->depth - new stdClass();
$Template->display("index.php");
and within template you would use traditional php like so:
<?php $this->_include("header.php") ?>
<ul>
<?php foreach($this->arrays as $a): ?>
<li><?php echo $this->bold($a) ?></li>
<?php endforeach; ?>
</ul>
This also allows you to have includes within templates that still have the $this keyword access to then include themselves, sort of recursion (but its not).
Then, don't pro-grammatically create a cache as there is nothing to be cached, you should use memcached which stores pre compiled source code within the memory skipping a large portion of compile / interpret time

If I'm not worried about caching or other advanced topics that would push me to an established template engine like smarty, I find that PHP itself is a great template engine. Just set variables in a script like normal and then include your template file
$name = 'Eric';
$locations = array('Germany', 'Panama', 'China');
include('templates/main.template.php');
main.tempate.php uses an alternative php tag syntax that is pretty easy for non php people to use, just tell them to ignore anything wrapped in a php tag :)
<h2>Your name is <?php echo $name; ?></h2>
<?php if(!empty($locations)): ?>
<ol>
<?php foreach($locations as $location): ?>
<li><?php echo $location; ?></li>
<?php endforeach; ?>
</ol>
<?php endif; ?>
<p> ... page continues ... </p>

I had a very basic answer to something KINDA like this back before I started using DOMXPath.
class is something like this (not sure if it works quite like what you want but food for thought as it works very simple
<?php
class template{
private $template;
function __CONSTRUCT($template)
{
//load a template
$this->template = file_get_contents($template);
}
function __DESTRUCT()
{
//echo it on object destruction
echo $this->template;
}
function set($element,$data)
{
//replace the element formatted however you like with whatever data
$this->template = str_replace("[".$element."]",$data,$this->template);
}
}
?>
with this class you would just create the object with whatever template you wanted and use the set function to place all your data.
simple loops after the object is created can probably accomplish your goal.
good luck

Smarty :) ...
php:
$smarty->assign("people",$peopleArray)
smarty template:
{foreach $people as $person}
<b>{$person.name}</b> {$person.surname}<br />
{/foreach}
Couple other things to do but that's what smarty will be like essentially.

Use Smarty.

Related

Is it possible to pass a function as another function parameter? [duplicate]

I need to pass a function as a parameter to another function and then call the passed function from within the function...This is probably easier for me to explain in code..I basically want to do something like this:
function ($functionToBeCalled)
{
call($functionToBeCalled,additional_params);
}
Is there a way to do that.. I am using PHP 4.3.9
Thanks!
I think you are looking for call_user_func.
An example from the PHP Manual:
<?php
function barber($type) {
echo "You wanted a $type haircut, no problem";
}
call_user_func('barber', "mushroom");
call_user_func('barber', "shave");
?>
function foo($function) {
$function(" World");
}
function bar($params) {
echo "Hello".$params;
}
$variable = 'bar';
foo($variable);
Additionally, you can do it this way. See variable functions.
In php this is very simple.
<?php
function here() {
print 'here';
}
function dynamo($name) {
$name();
}
//Will work
dynamo('here');
//Will fail
dynamo('not_here');
I know the original question asked about PHP 4.3, but now it's a few years later and I just wanted to advocate for my preferred way to do this in PHP 5.3 or higher.
PHP 5.3+ now includes support for anonymous functions (closures), so you can use some standard functional programming techniques, as in languages like JavaScript and Ruby (with a few caveats). Rewriting the call_user_func example above in "closure style" would look like this, which I find more elegant:
$barber = function($type) {
echo "You wanted a $type haircut, no problem\n";
};
$barber('mushroom');
$barber('shave');
Obviously, this doesn't buy you much in this example - the power and flexibility comes when you pass these anonymous functions to other functions (as in the original question). So you can do something like:
$barber_cost = function($quantity) {
return $quantity * 15;
};
$candy_shop_cost = function($quantity) {
return $quantity * 4.50; // It's Moonstruck chocolate, ok?
};
function get_cost($cost_fn, $quantity) {
return $cost_fn($quantity);
}
echo '3 haircuts cost $' . get_cost($barber_cost, 3) . "\n";
echo '6 candies cost $' . get_cost($candy_shop_cost, 6) . "\n";
This could be done with call_user_func, of course, but I find this syntax much clearer, especially once namespaces and member variables get involved.
One caveat: I'll be the first to admit I don't know exactly what's going on here, but you can't always call a closure contained in a member or static variable, and possibly in some other cases. But reassigning it to a local variable will allow it to be invoked. So, for example, this will give you an error:
$some_value = \SomeNamespace\SomeClass::$closure($arg1, $arg2);
But this simple workaround fixes the issue:
$the_closure = \SomeNamespace\SomeClass::$closure;
$some_value = $the_closure($arg1, $arg2);
You could also use call_user_func_array(). It allows you to pass an array of parameters as the second parameter so you don't have to know exactly how many variables you're passing.
If you need pass function with parameter as parameter, you can try this:
function foo ($param1){
return $param1;
}
function bar ($foo_function, $foo_param){
echo $foo_function($foo_param);
}
//call function bar
bar('foo', 'Hi there'); //this will print: 'Hi there'
phpfiddle example
Hope it'll be helpful...
If you want to do this inside a PHP Class, take a look at this code:
// Create a sample class
class Sample
{
// Our class displays 2 lists, one for images and one for paragraphs
function __construct( $args ) {
$images = $args['images'];
$items = $args['items'];
?>
<div>
<?php
// Display a list of images
$this->loop( $images, 'image' );
// notice how we pass the name of the function as a string
// Display a list of paragraphs
$this->loop( $items, 'content' );
// notice how we pass the name of the function as a string
?>
</div>
<?php
}
// Reuse the loop
function loop( $items, $type ) {
// if there are items
if ( $items ) {
// iterate through each one
foreach ( $items as $item ) {
// pass the current item to the function
$this->$type( $item );
// becomes $this->image
// becomes $this->content
}
}
}
// Display a single image
function image( $item ) {
?>
<img src="<?php echo $item['url']; ?>">
<?php
}
// Display a single paragraph
function content( $item ) {
?>
<p><?php echo $item; ?></p>
<?php
}
}
// Create 2 sample arrays
$images = array( 'image-1.jpg', 'image-2.jpg', 'image-3.jpg' );
$items = array( 'sample one', 'sample two', 'sample three' );
// Create a sample object to pass my arrays to Sample
$elements = { 'images' => $images, 'items' => $items }
// Create an Instance of Sample and pass the $elements as arguments
new Sample( $elements );

Pass multiple parameters to a blade directive

I'm trying to create a blade directive to highlight some words that will return from my search query.
This is my blade directive:
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Blade::directive('highlight', function($expression, $string){
$expressionValues = preg_split('/\s+/', $expression);
foreach ($expressionValues as $value) {
$string = str_replace($value, "<b>".$value."</b>", $string);
}
return "<?php echo {$string}; ?>";
});
}
public function register()
{
}
}
And I call in blade like this:
#highlight('ho', 'house')
But, this erros is following me:
Missing argument 2 for App\Providers\AppServiceProvider::App\Providers\{closure}()
How to solve it?
For associative arrays, eval() may be the easiest. But its use is adverted as dangerous, because it's like your opening a hole, a needle for code execution. In same time eval() execute at runtime, well it store the code to be executed in database (caching [well it mean it cache compiled byte code]). That's additional overhead, so performance will take a hit. Here's a nice paper on the topic [didn't read or get into the details]) https://link.springer.com/chapter/10.1007%2F978-981-10-3935-5_12.
Well here I may have got you!, there is no performance difference at server serving performance, because views are cached, and generated only when you change them. Directives are translated to php code and in another process they are cached. (you can find the generated view in storage/framework/views)
So for
Blade::directive('custom', function ($expression) {
eval("\$myarray = [$expression];");
// do something with $myarray
return "<?php echo ..";
});
It's just ok. There is nothing to talk about for eval() and performance (it's done and cached, and the generated php code is the one that will run over and over (just make sure the returned php code by the directive doesn't hold eval(), unless there is a reason). Using eval() directly (which will be used for different request over and over) will impact performance.
(I wanted to talk about eval(), I think those are useful info)
as it is we can parse array form ["sometin" => "i should be sting", "" => "", ...].
eval("\$array = $expression;");
// then we can do what we want with $array
However we can't pass variables. ex: #directive(["s" => $var])
if we use eval, $var will be undefined in the directive function scope. (don't forget that directive are just a way to generate tempalte beautifully, and turning the ugly (not really ugly) php code into such directive. In fact it's the inverse, we are turning the beautiful directive to the php code that will be executed at the end. And all you are doing here is generating, building, writing the expression that will form the final php pages or files.)
What you can do instead is to pass the variable in this way
["s" => "$var"] , so it will pass through eval. And then in your return statement, use it example:
return "<?php echo ".$array['s'].";?>";
when the template will be generated this will be <?php echo $var;?>;
Remember, if you decide to use eval, never use it within the returned string! or maybe you want to in some cases.
Another solution
(which is easy) along to the proposed parsing solutions, is to use a json format to passe data to your directive, and just use json_decode. (it just came to me)
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Blade::directive('highlight', function($json_expression){
$myArray = json_decode($json_expression)
// do something with the array
});
}
public function register()
{
}
}
Here an example where I needed to do so:
the goal is to automate this
#php
$logo = !empty($logo) ? $logo : 'logo';
$width = !empty($width) ? $width : 'logo';
//... // wait i will not always keep doing that ! h h
#endphp // imaging we do that for all different number of view components ...
and so I wrote this directive:
public function boot()
{
Blade::directive('varSet', function ($expr) {
$array = json_decode($expr, true);
$p = '<?php ';
foreach ($array as $key => $val) {
if (is_string($val)) {
$p .= "\$$key = isset(\$$key) && !empty(\$$key) ? \$$key : '$val'; ";
} else {
$p .= "\$$key = isset(\$$key) && !empty(\$$key) ? \$$key : $val; ";
}
}
$p .= '?>';
return $p;
});
}
We use it like this:
#varSet({
"logo": "logo",
"width": 78,
"height": 22
})// hi my cool directive. that's slick.
Why this form work? it get passed as a string template like this
"""
{\n
"logo": "logo",\n
"width": 78,\n
"height": 22\n
}
"""
For using in template variable pass them as string like that "$var", same as what we did with eval.
For parsing from ["" => "", ..] format may be eval() is the best choice. Remember that this is done at template generation which are cached later, and not updated, until we make change again. And remember to not use eval() within the return ; directive instruction. (only if your application need that)
for just multi arguments, and so not an array:
A function like that will do the job:
public static function parseMultipleArgs($expression)
{
return collect(explode(',', $expression))->map(function ($item) {
return trim($item);
});
}
or
public static function parseMultipleArgs($expression)
{
$ar = explode(',', $expression);
$l = len($ar);
if($l == 1) return $ar[0];
for($i = 0; $i < $l; $i++){$ar[$i] = trim($ar[$i])}
return $ar;
}
and you can tweak them as you like, using str_replace to remove things like () ...etc [in short we workout our own parsing. RegEx can be helpful. And depend on what we want to achieve.
All the above are way to parse entries and separate them into variables you use for generating the template. And so for making your return statement.
WHAT IF ALL YOU WANT IS TO HAVE YOUR DIRECTIVE TAKE AN ARRAY WITH VARIABLES FROM THE VIEW SCOPE:
like in #section('', ["var" => $varValue])
Well here particulary we use the multi arguments parsing, then we recover ["" => ..] expression separately (and here is not the point).
The point is when you want to pass an array to be used in your code (view scope). You just use it as it is. (it can be confusing).
ex:
Blade::directive("do", function ($expr) {
return "<?php someFunctionFromMyGlobalOrViewScopThatTakeArrayAsParameter($expr); ?>
});
This will evaluate to
<?php someFunctionFromMyGlobalOrViewScopThatTakeArrayAsParameter(["name" => $user->name, .......]); ?>
And so all will work all right. I took an example where we use a function, you can put all a logic. Directives are just a way to write view in a more beautiful way. Also it allow for pre-view processing and generation. Quiet nice.
Blade::directive('custom', function ($expression) {
eval("\$params = [$expression];");
list($param1, $param2, $param3) = $params;
// Great coding stuff here
});
and in blade template:
#custom('param1', 'param2', 'param3')
I was searching for this exact solution, then decided to try something different after reading everything and ended up coming up with the solution you and I were both looking for.
No need for JSON workarounds, explodes, associative arrays, etc... unless you want that functionality for something more complex later.
Because Blade is just writing out PHP code to be interpreted later, whatever you've placed into your #highlight directive is the exact PHP code in string format that will be interpreted later.
What to Do:
Make and register a helper function that you can call throughout your application. Then use the helper function in your blade directive.
Helper Definition:
if(!function_exists('highlight')){
function highlight($expression, $string){
$expressionValues = preg_split('/\s+/', $expression);
foreach ($expressionValues as $value) {
$string = str_replace($value, "<b>".$value."</b>", $string);
}
return $string;
}
}
Blade Directive:
Blade::directive('highlight', function ($passedDirectiveString){
return "<?php echo highlight($passedDirectiveString);?>";
});
Usage (Example):
<div>
#highlight('ho', 'house')
</div>
Understanding:
This is equivalent to writing out:
<div>
{! highlight('ho', 'house') !}
</div>
I think you can only pass one parameter. It's not pretty but you could pass your parameters as an array like so:
#highlight(['expression' => 'ho', 'string' => 'house'])
So your directive could be
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Blade::directive('highlight', function($array){
$expressionValues = preg_split('/\s+/', $array['expression']);
foreach ($expressionValues as $value) {
$array['string'] = str_replace($value, "<b>".$value."</b>", $array['string']);
}
return "<?php echo {$array['string']}; ?>";
});
}
public function register()
{
}
}
Found it here: https://laracasts.com/discuss/channels/laravel/how-to-do-this-blade-directive
The best way to accomplish this task is exactly as #Everrett suggested.
I checked through the blade code, and this is exactly how the laravel team has to do it as well.
If you look through vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesHelpers.php, at the compileDd function, you'll notice that they use $arguments instead of $expression like they do in all of the other compile functions found in vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/
// CompilesHelpers.php
protected function compileDd($arguments)
{
return "<?php dd{$arguments}; ?>";
}
//CompilesConditionals.php
protected function compileIf($expression)
{
return "<?php if{$expression}: ?>";
}
And if you look at vendor/symfony/var-dumper/Resources/functions/dump.php you'll see that Laravel handles variable arguments with ... splat notation in the dd function.
if (!function_exists('dd')) {
function dd(...$vars)
{
}}
So you could do a directive like: (I put my custom function in app\helpers)
If you do the same, you need to make sure to escape the backslashes.
Blade::directive('customFunc', function ($expression) {
return "<?php \\App\\Helpers\\customFunc({$arguments}); ?>";
});
and a custom function like:
/**
* Custom function to demonstrate usage
* #param mixed ...$args
* #return void
*/
function customFunc(...$args): void {
// Extract variables //
// Use pad to get expected args, and set unset to null //
list($arg1, $arg2, $arg3) = array_pad($args, 3, null);
// Echo out args //
echo "arg1: ${arg1} | arg2: ${arg2} | arg3: {$arg3}";
}
run php artisan view:clear
And then use the directive:
<div>
#customFunc('hello','wonderful', 'world')
</div>
// Returns:
arg1: hello | arg2: wonderful | arg3: world
// Using
<div>
#customFunc('hello', 'world')
</div>
// Returns:
arg1: hello | arg2: world | arg3:
The best reason to do it this way is so that if your function evolves or changes, you only need to modify the underlining function. You wont have to clear views every time you change the code.
I found an alternative approach to accessing View variables within a Blade Directive.
I wanted to check whether a given string appeared as an array key in a variable accessible in the view scope.
As the Blade Directive returns PHP which is evaluated later, it is possible to 'trick' the Directive by breaking up a variable name so that it doesn't try to parse it.
For example:
Blade::directive('getElementProps', function ($elementID) {
return "<?php
// Reference the $elementData variable
// by splitting its name so it's not parsed here
if (array_key_exists($elementID, $" . "elementData)) {
echo $" . "elementData[$elementID];
}
?>";
});
In this example we have split the $elementData variable name so the Blade Directive treats it like a string. When the concatenated string is returned to the blade it will be evaluated as the variable.
Blade::directive('highlight', function($arguments){
list($arg1, $arg2) = explode(',',str_replace(['(',')',' ', "'"], '', $arguments));
$expressionValues = preg_split('/\s+/', $arg1);
$output = "";
foreach ($expressionValues as $value) {
$output .= str_replace($value, "<b>".$value."</b>", $arg2);
}
return "<?php echo \"{$output}\"; ?>";
});
The value received on blade directive function is a sting, so, you must parse to get the values:
BLADE
#date($date, 'd-M-Y')
AppServiceProvider
Blade::directive('date', function ($str) {
// $str = "$date, 'd-M-Y'";
$data = explode(',',str_replace(' ', '', $str));
//$data = ["$date", "'d-M-Y'"]
$date = $data[0];
$format = $data[1];
return "<?= date_format(date_create($date), $format) ?>";
});
If you want to reference variables within a custom blade directive you may not need to pass them directly to the directive. I solved this problem by calling the blade directive from within a blade component. Blade components have local variable scope and so you can simply pass all the variables you need within the call to the blade component (without polluting your view scope). This is sufficient so long as you don't actually need to modify the variables or use them for control logic in your directive.
//view.blade.php
#component('my-component',['myVar1'=> $something, 'myVar2'=>$somethingElse])
#endcomponent
//my-component.blade.php
#myBladeDirective('Two variables accessible')
//Boot method of relevant service provider
Blade::directive('myBladeDirective', function ($someVar) {
return "<?php echo $someVar : {$myVar1} and {$myVar2};?>
});

how to implement a custom foreach template view method

I was coding a TemplateView Class which is replacing #somevariable# placeholders with it's controller set correspondent. ViewHelpers are called via placeholders that simulate a function like #headerFiles()# for example.
The thing missing is a foreach loop definition like for example the smarty
{foreach from=$var key=index item=value} definition.
Until now I was iterating over a database object inside the controller and having a heredok or string concatenation assigned to the view. So there is a lot of html inside the controllers which I think should not be the controllers job.
I already implemented smarty into the app for testing and it's working fine - But when I decide to really use it I would have to change so many things like all partial templates would have to be .tpl files and all template directories would have to be merged ? or in one place and so on.
So my question is:
Are there any good examples or codesnippets I could use to implement a custom foreach loop method ? Before I have to change everything to use smarty. I also want to avoid using plain php like <?php foreach(): endforeach; ?>
UPDATE:
what concerns would one have going this direction ?
In a template:
#foreach array#
if($key !== 'fart'){
echo $val;
}
#endforeach#
class Foreacher {
private $template;
protected $array = array('fart' => 'poop', 'foo', 'bar');
public function __construct($template){
$this->template = file_get_contents($template);
}
public function parse(){
if(preg_match_all('/#foreach ([\w]+)#(.*?)#endforeach#/is', $this->template, $matches, PREG_SET_ORDER)){
$var = $matches[0][1];
$freach = "<?php foreach(\$this->$var as \$key => \$val){";
$freach .= $matches[0][2];
$freach .= "} ?>";
$parsed = str_replace($matches[0][0], $freach, $this->template);
$this->render($parsed);
}
}
public function __get($var){
if(isset($this->$var)){
return $this->$var;
}
return null;
}
protected function render($string){
$tmp = tmpfile();
fwrite($tmp, $string);
fseek($tmp, 0);
ob_start();
$file = stream_get_meta_data($tmp);
include $file['uri'];
$data = ob_get_clean();
fclose($tmp);
echo $data;
}
}

PHP: Class property chaining in variable variables

So, I have a object with structure similar to below, all of which are returned to me as stdClass objects
$person->contact->phone;
$person->contact->email;
$person->contact->address->line_1;
$person->contact->address->line_2;
$person->dob->day;
$person->dob->month;
$person->dob->year;
$album->name;
$album->image->height;
$album->image->width;
$album->artist->name;
$album->artist->id;
etc... (note these examples are not linked together).
Is it possible to use variable variables to call contact->phone as a direct property of $person?
For example:
$property = 'contact->phone';
echo $person->$property;
This will not work as is and throws a E_NOTICE so I am trying to work out an alternative method to achieve this.
Any ideas?
In response to answers relating to proxy methods:
And I would except this object is from a library and am using it to populate a new object with an array map as follows:
array(
'contactPhone' => 'contact->phone',
'contactEmail' => 'contact->email'
);
and then foreaching through the map to populate the new object. I guess I could envole the mapper instead...
If i was you I would create a simple method ->property(); that returns $this->contact->phone
Is it possible to use variable variables to call contact->phone as a direct property of $person?
It's not possible to use expressions as variable variable names.
But you can always cheat:
class xyz {
function __get($name) {
if (strpos($name, "->")) {
foreach (explode("->", $name) as $name) {
$var = isset($var) ? $var->$name : $this->$name;
}
return $var;
}
else return $this->$name;
}
}
try this code
$property = $contact->phone;
echo $person->$property;
I think this is a bad thing to to as it leads to unreadable code is is plain wrong on other levels too, but in general if you need to include variables in the object syntax you should wrap it in braces so that it gets parsed first.
For example:
$property = 'contact->phone';
echo $person->{$property};
The same applies if you need to access an object that has disalowed characters in the name which can happen with SimpleXML objects regularly.
$xml->{a-disallowed-field}
If it is legal it does not mean it is also moral. And this is the main issue with PHP, yes, you can do almost whatever you can think of, but that does not make it right. Take a look at the law of demeter:
Law of Demeter
try this if you really really want to:
json_decode(json_encode($person),true);
you will be able to parse it as an array not an object but it does your job for the getting not for the setting.
EDIT:
class Adapter {
public static function adapt($data,$type) {
$vars = get_class_vars($type);
if(class_exists($type)) {
$adaptedData = new $type();
} else {
print_R($data);
throw new Exception("Class ".$type." does not exist for data ".$data);
}
$vars = array_keys($vars);
foreach($vars as $v) {
if($v) {
if(is_object($data->$v)) {
// I store the $type inside the object
$adaptedData->$v = Adapter::adapt($data->$v,$data->$v->type);
} else {
$adaptedData->$v = $data->$v;
}
}
}
return $adaptedData;
}
}
OOP is much about shielding the object's internals from the outside world. What you try to do here is provide a way to publicize the innards of the phone through the person interface. That's not nice.
If you want a convenient way to get "all" the properties, you may want to write an explicit set of convenience functions for that, maybe wrapped in another class if you like. That way you can evolve the supported utilities without having to touch (and possibly break) the core data structures:
class conv {
static function phone( $person ) {
return $person->contact->phone;
}
}
// imagine getting a Person from db
$person = getpersonfromDB();
print conv::phone( $p );
If ever you need a more specialized function, you add it to the utilities. This is imho the nices solution: separate the convenience from the core to decrease complexity, and increase maintainability/understandability.
Another way is to 'extend' the Person class with conveniences, built around the core class' innards:
class ConvPerson extends Person {
function __construct( $person ) {
Person::__construct( $person->contact, $person->name, ... );
}
function phone() { return $this->contact->phone; }
}
// imagine getting a Person from db
$person = getpersonfromDB();
$p=new ConvPerson( $person );
print $p->phone();
You could use type casting to change the object to an array.
$person = (array) $person;
echo $person['contact']['phone'];
In most cases where you have nested internal objects, it might be a good time to re-evaluate your data structures.
In the example above, person has contact and dob. The contact also contains address. Trying to access the data from the uppermost level is not uncommon when writing complex database applications. However, you might find your the best solution to this is to consolidate data up into the person class instead of trying to essentially "mine" into the internal objects.
As much as I hate saying it, you could do an eval :
foreach ($properties as $property) {
echo eval("return \$person->$property;");
}
Besides making function getPhone(){return $this->contact->phone;} you could make a magic method that would look through internal objects for requested field. Do remember that magic methods are somewhat slow though.
class Person {
private $fields = array();
//...
public function __get($name) {
if (empty($this->fields)) {
$this->fields = get_class_vars(__CLASS__);
}
//Cycle through properties and see if one of them contains requested field:
foreach ($this->fields as $propName => $default) {
if (is_object($this->$propName) && isset($this->$propName->$name)) {
return $this->$propName->$name;
}
}
return NULL;
//Or any other error handling
}
}
I have decided to scrap this whole approach and go with a more long-winded but cleaner and most probably more efficient. I wasn't too keen on this idea in the first place, and the majority has spoken on here to make my mind up for me. Thank for you for your answers.
Edit:
If you are interested:
public function __construct($data)
{
$this->_raw = $data;
}
public function getContactPhone()
{
return $this->contact->phone;
}
public function __get($name)
{
if (isset($this->$name)) {
return $this->$name;
}
if (isset($this->_raw->$name)) {
return $this->_raw->$name;
}
return null;
}
In case you use your object in a struct-like way, you can model a 'path' to the requested node explicitly. You can then 'decorate' your objects with the same retrieval code.
An example of 'retrieval only' decoration code:
function retrieve( $obj, $path ) {
$element=$obj;
foreach( $path as $step ) {
$element=$element[$step];
}
return $element;
}
function decorate( $decos, &$object ) {
foreach( $decos as $name=>$path ) {
$object[$name]=retrieve($object,$path);
}
}
$o=array(
"id"=>array("name"=>"Ben","surname"=>"Taylor"),
"contact"=>array( "phone"=>"0101010" )
);
$decorations=array(
"phone"=>array("contact","phone"),
"name"=>array("id","name")
);
// this is where the action is
decorate( $decorations, &$o);
print $o->name;
print $o->phone;
(find it on codepad)
If you know the two function's names, could you do this? (not tested)
$a = [
'contactPhone' => 'contact->phone',
'contactEmail' => 'contact->email'
];
foreach ($a as $name => $chain) {
$std = new stdClass();
list($f1, $f2) = explode('->', $chain);
echo $std->{$f1}()->{$f2}(); // This works
}
If it's not always two functions, you could hack it more to make it work. Point is, you can call chained functions using variable variables, as long as you use the bracket format.
Simplest and cleanest way I know of.
function getValueByPath($obj,$path) {
return eval('return $obj->'.$path.';');
}
Usage
echo getValueByPath($person,'contact->email');
// Returns the value of that object path

How to make a php template engine?

I need to make a small and simple php template engine I searched a lot and many of them were too complex to understand and I don't want to use smarty and other similar engines, I have got some idea from Stack Overflow like this:
$template = file_get_contents('file.html');
$array = array('var1' => 'value',
'txt' => 'text');
foreach($array as $key => $value)
{
$template = str_replace('{'.$key.'}', $value, $template);
}
echo $template;
Now instead of echo the template I just want to add include "file.html" and it will display the file with correct variable values and I want to put the engine in a separate place and just include it in the template what I want to use it declare the array and at the end include the html file like phpbb. Sorry I am asking to much but can anyone just explain the basic concept behind this?
EDIT: Well let me be frank i am making a forum script and i have got tons of ideas for it but i want make its template system like phpbb so i need a separate template engine custom one if you can help then please you are invited to work with me. sorry for the ad.. :p
file.html:
<html>
<body>
<h3>Hi there, <?php echo $name ?></h3>
</body>
</html>
file.php:
<?php
$name = "Keshav";
include('file.html');
?>
Doesn't get simpler than this. Yes, it uses global variables, but if simple is the name of the game, this is it. Simply visit 'http://example.com/file.php' and off you go.
Now, if you want the user to see 'file.html' in the browser's address bar, you'd have to configure your webserver to treat .html files as PHP scripts, which is a little more complicated, but definitely doable. Once that's done, you can combine both files into a single one:
file.html:
<?php
$name = "Keshav";
?>
<html>
<body>
<h3>Hi there, <?php echo $name ?></h3>
</body>
</html>
What if, for a script easier to maintain, you move those to functions?
something like this:
<?php
function get_content($file, $data)
{
$template = file_get_contents($file);
foreach($data as $key => $value)
{
$template = str_replace('{'.$key.'}', $value, $template);
}
return $template;
}
And you can use it this way:
<?php
$file = '/path/to/your/file.php';
$data = = array('var1' => 'value',
'txt' => 'text');
echo get_content($file, $data);
Once you iron out all bugs, fix huge performance problem you're getting yourself into, you'll end up with template engine just like Smarty and otheres.
Such find'n'replace approach is much slower than compilation to PHP. It does not handle escaping very well (you'll run into XSS problems). It will be quite difficult to add conditions and loops, and you will need them sooner or later.
<?php
class view {
private $file;
private $vars = array();
public function __construct($file) {
$this->file = $file;
}
public function __set($key, $val) {
$this->vars[$key] = $val;
}
public function __get($key, $val) {
return (isset($this->vars[$key])) ? $this->vars[$key] : null;
}
public function render() {
//start output buffering (so we can return the content)
ob_start();
//bring all variables into "local" variables using "variable variable names"
foreach($this->vars as $k => $v) {
$$k = $v;
}
//include view
include($this->file);
$str = ob_get_contents();//get teh entire view.
ob_end_clean();//stop output buffering
return $str;
}
}
Here's how to use it:
<?php
$view = new view('userprofile.php');
$view->name = 'Afflicto';
$view->bio = "I'm a geek.";
echo $view->render();

Categories