how blade can access to defined varibles outside of itself - php

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.

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.

Undefined variable after wrapped include in PHP

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}'");
}
}

php how to access language variable inside a function when the language page has already been included?

My php script includes another en.php file which contains the required english strings. This same page also calls a html page which uses the file and formats it using the contents of the en.php file.
I have a function in this script which references variables defined in the included script but I am getting error messages of the variable not being found. If I reference the variable outside the function, the variable is accessed correctly. Why can I not access these variables inside the function?
en.php
<?php
$lang_yes = 'Yes';
$lang_no = 'No';
?>
example.php
<?php
include_once('addons/assq/lang/en.php');
echo $lang_yes;
$q1 = convertToYesOrNoString(0);
echo $q1;
function convertToYesOrNoString($value){
//include_once('addons/assq/lang/en.php');
if ($value == 0){
return $lang_no;
}else if ($value == 1){
return $lang_yes;
}else{
return "---";
}
}
?>
My output is as follows:
Yes
Undefined variable: lang_no in example.php on the line in the function
I tried including the en.php directly into the function but that did not work either. How can I access these variables inside my function while including the file as implemented above?
You can either define it as a constant, pass it as an argument or declare it as a global within the function:
function convertToYesOrNoString($value){
global $lang_no, $lang_yes;
//...
}
That's a scope issue. That variable $lang_no will not be accessed under that function , you need to pass that as a parameter instead to the function definition.
function convertToYesOrNoString($value,$lang_no){ //<--- Like this.
Since you have mentioned that you have a lot of parameters .. you can write a turnaround like this...
Your en.php
<?php
//Map all those variables inside an array as key-value pair. as shown
$varArray=array('lang_yes'=>'Yes','lang_no'=>'No');
Some test.php
<?php
include('en.php');
function convertToYesOrNoString($varArray)
{
extract($varArray);
echo $lang_yes; // "prints" Yes
echo $lang_no; // "prints" No
}
convertToYesOrNoString($varArray);

Include PHP file with parameter

newbie in PHP here, sorry for troubling you.
I want to ask something, if I want to include a php page, can I use parameter to define the page which I'll be calling?
Let's say I have to include a title part in my template page. Every page has different title which will be represented as an image. So,
is it possible for me to call something <?php #include('title.php',<image title>); ?> inside my template.php?
so the include will return title page with specific image to represent the title.
thank you guys.
An included page will see all the variables for the current scope.
$title = 'image title';
include('title.php');
Then in your title.php file that variable is there.
echo '<h1>'.$title.'</h1>';
It's recommended to check if the variable isset() before using it. Like this.
if(isset($title))
{
echo '<h1>'.$title.'</h1>';
}
else
{
// handle an error
}
EDIT:
Alternatively, if you want to use a function call approach. It's best to make the function specific to activity being performed by the included file.
function do_title($title)
{
include('title.php'); // note: $title will be a local variable
}
Not sure if this is what you're looking for, but you can create a function to include the file and pass a variable.
function includeFile($file, $param) {
echo $param;
include_once($file);
}
includeFile('title.php', "title");
In your included file, you could do this:
<?php
return function($title) {
do_title_things($title);
do_other_things();
};
function do_title_things($title) {
// ...
}
function do_other_things() {
// ...
}
Then, you could pass the parameter as such:
$callback = include('myfile.php');
$callback('new title');
Another more commonly used pattern is to make a new scope for variables to be passed in:
function include_with_vars($file, $params) {
extract($params);
include($file);
}
include_with_vars('myfile.php', array(
'title' => 'my title'
));
The included page will already have access to those variables defined prior to the include. If you require include specific variables, I suggest defining those variables on the page to be included

Including a file multiple times (for templates)

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?

Categories