I am working on a simple template engine, and I was wondering if it's feasible to include the template file multiple times, once for each time the template is rendered. It basically goes like this:
function rendering_function_in_rendering_class()
{
include $path_to_templates . get_class($this) . 'template.php';
}
And then in the template file:
<h1>Hello, <?php echo $this->awesomename ?>!</h1>
This function does exactly what you need:
<?php
function embed($file, $vars) {
ob_start();
extract($vars, EXTR_SKIP);
include($file);
$content = ob_get_contents();
ob_end_clean();
return $content;
}
?>
It takes file path as a first parameter and key/value array of variables which will be extract into the scope such that your template will be able to use them directly in HTML like this:
<h1><?php print $title; ?></h1>
No, functions in PHP may only be defined once. However, you can make more than one instance of each class:
$this1=new rendering();
$this2=new rendering();
echo $this1->awesomename;
echo $this2->awesomename;
Or use a function instide a class without the class being initialized:
$rendering::rendering_function_in_rendering_class();
Does that answer your question?
Related
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.
i want to create a template engine with php regex
my template engine is the following code
<?php
class tmp
{
public function assign($name,$val){
$GLOBALS[$name]=$val;
}
public function compile($buffer){
$buffer= '?> '.preg_replace('~\{\!\!\s*\$(\w+)\s*\!\!\}~', '<?php echo $GLOBALS["$1"]; ?>', $buffer);
file_put_contents('compiled.php', $buffer);
return $buffer;
}
public function run($run){
return eval($run);
}
}
?>
and my theme is the following code
{!! $bar !!}
and compiled theme is
?>
<?php echo $GLOBALS["bar"]; ?>
I access to vars with $GLOBALS
but in blade the compiled code is as follows
<?php echo $bar; ?>
how blade can access to defined vars directly with their name?
I can't confirm this is how blade is doing it, though I suspect it would be, but basically extract does that exactly what you are asking:
http://php.net/manual/en/function.extract.php
It takes an array of variables and inserts directly into the symbol tables so they be accessed directly. Used in combination with get_defined_variables() it becomes very useful.
I'm using PHP for web development. I'm using the following function to wrap the include of a view:
<?php
function render($templateFile) {
$templateDir = 'views/';
if (file_exists($templateDir . $templateFile)) {
include $templateDir . $templateFile;
} else {
throw new Exception("Template '{$templateFile}' couldn't be found " .
"in '{$templateDir}'");
}
}
?>
Although this seems right to me, there is a really unexpected behavior: when I define a variable to something (e.g. an array) and use render for including a view that uses that variable, I get an undefined variable error. But when I explicitely use include there is no error at all and things are just fine.
This is the script that calls render:
<?php
include 'lib/render.php'; // Includes the function above.
$names = array('Trevor', 'Michael', 'Franklin');
render('names.html'); // Error, but "include 'views/names.html'" works fine.
?>
And this is the file that uses the $names variable:
<html>
<head>
<title>Names</title>
</head>
<body>
<ol>
<?php foreach ($names as $name): ?>
<li><?php echo $name; ?></li>
<?php endforeach; ?>
</ol>
</body>
</html>
Help will be very much appreciated.
This is from the PHP documentation on the include function (c.f. http://us1.php.net/manual/en/function.include.php):
When a file is included, the code it contains inherits the variable
scope of the line on which the include occurs. Any variables available
at that line in the calling file will be available within the called
file, from that point forward. However, all functions and classes
defined in the included file have the global scope.
And also:
If the include occurs inside a function within the calling file, then
all of the code contained in the called file will behave as though it
had been defined inside that function. So, it will follow the variable
scope of that function.
So, if your render function can't access $names, then neither can your included file.
A possible solution would be to pass the parameters you want to be able to access in your view template, to your render function. So, something like this:
function render($templateFile, $params=array()) {
$templateDir = 'views/';
if (file_exists($templateDir . $templateFile)) {
include $templateDir . $templateFile;
} else {
throw new Exception("Template '{$templateFile}' couldn't be found " .
"in '{$templateDir}'");
}
}
Then, pass them like this:
$names = array('Trevor', 'Michael', 'Franklin');
render('names.html', array("names" => $names));
And use them in your view template like this:
<html>
<head>
<title>Names</title>
</head>
<body>
<ol>
<?php foreach ($params['names'] as $name): ?>
<li><?php echo $name; ?></li>
<?php endforeach; ?>
</ol>
</body>
</html>
There are probably better solutions to this, like putting your render function into a View class. Then you can call the View class function from inside your template file, and access parameters that way instead of just assuming there will be a $params variable in the view templates scope. But, this is the simplest solution.
The problem is, when you include the file directly using include 'views/names.html' the variable $name remains in the same files scope. Hence, it works. But when the include is done through the function, the varibale $name remains out of scope inside the function. So it doesn't work. For example, declare $names as global inside the function and it will work.
If you update the function like below you will see $names variable works.
function render($templateFile) {
global $names; // declares the global $names variable to use in the included files
$templateDir = 'views/';
if (file_exists($templateDir . $templateFile)) {
include $templateDir . $templateFile;
} else {
throw new Exception("Template '{$templateFile}' couldn't be found " .
"in '{$templateDir}'");
}
}
Question Updated
I am building an MVC framework, for my templates and views, I will have a main page template file and my views will be included into this template.
The only way I have seen to do this is to use output buffereing
ob_start();
include 'userProfile.php';
$content = ob_get_clean();
Is there any other way of doing this? I think output buffering is not the best on performance as it uses a lot of memory
Here is a sample controller, the $this->view->load('userProfile', $profileData);
is the part that will be loaded using output biffering so that it can be included into the main template below into the $content part
view class
public function load($view,$data = null) {
if($data) {
$this->data = $data;
extract($data);
} elseif($this->data != null) {
extract($this->data);
}
ob_start();
require(APP_PATH . "Views/$view.php");
$content = ob_get_clean();
}
controller
/**
* Example Controller
*/
class User_Controller extends Core_Controller {
// domain.com/user/id-53463463
function profile($userId)
{
// load a Model
$this->loadModel('profile');
//GET data from a Model
$profileData = $this->profile_model->getProfile($userId);
// load view file
$this->view->load('userProfile', $profileData);
}
}
main site template
<html>
<head>
</head>
<body>
<?php echo $content; ?>
</body>
</html>
Using a template system is not necessarily tied to output buffering. There are a couple of things in the example code you give that should certainly not be taken for granted:
One:
flushblocks(); // what does this do??
And two:
$s = ob_get_clean();
Why does the code capture the template output into a variable? Is it necessary to do some processing on this before outputting it? If not, you could simply lose the output buffering calls and let the output be sent to the browser immediately.
I have two files like below
SomeClass.php
class SomeClass {
public function display() {
$content = file_get_contents("helloworld.php");
eval($content);
}
public function helloWorld() {
echo "hello World!";
}
}
helloworld.php
<?php
$this->helloWorld() ;
?>
<p>It's html expression</p>
As you can see, I tried to execute helloworld.php in the display function.
Of course, It occur an error because the html tag is placed in the display function.
Is there any good way to execute helloworld.php text in the display function preserving helloworld.php code?
If you're trying to execute a specific file in the context of the current code, why not use include or require?
Remember, if eval is the answer, the question is wrong.
If you really want to use eval here,
eval('?>' . $content);
should work. Yes, you can close and reopen the PHP tag inside. This is how certain template engines work.
You can capture it with output buffering.
ob_start();
include "helloworld.php";
$content = ob_get_contents();
ob_end_clean();
There is no way to do this, unless you want to do string concatination.
I tested this with a few minor changes to the helloworld.php file as such it works:
$this->helloWorld() ;
?><p>It's html expression</p>
This shows that the text is run raw as if it were hard included.
Now if you DONT or CANT change the open <?php tag, you can go one of two ways.
The easy way (String Concatenation):
public function display() {
$content = file_get_contents("helloworld.php");
eval('?>' . $content); //append a php close tag, so the file looks like "?><?php"
}
The harder way (String Replace):
public function display() {
$content = file_get_contents("helloworld.php");
//safely check the beginning of the file, if its an open php tag, remove it.
if('<?php' == substr($content, 0, 5)) {
$content = substr($content, 5);
}
eval($content);
}
you can use include or require for this purpose.