Thanks in advance for any and all advice.
I have one file (content file) where an array is created like so:
$config['plugins']['BannerRotate'] = array(
'container' => 'abroadView',
'arrows' => true,
'auto' => true,
'speed' => '15000',
'width' => '300px',
'height' => '220px',
'tags' => 'slider'
);
Which in turn is picked up by the so-called 'template system' and the layout is rendered with the page (and that array) as the argument (pretty standard).
That array is then used by the template system to generate a new object like so:
if(isset($GLOBALS['config']['plugins'])){
foreach($GLOBALS['config']['plugins'] as $plugin => $ary){
$$plugin = new Ispweb_Plugindaemon(CURRENTSRV,getcwd().'/',
$GLOBALS['config']['plugins'][$plugin],$plugin);
// this statement is simply the result of the eval statement below
}
}
So then, since the name of the plugin in this case is BannerRotate, the object is $BannerRotate (variable variable). I'm doing this so I can have multiple plugin objects per page. This object is then used to call the jQuery plugin using member function $BannerRotate->getJS(). These member function calls are located WITHIN the templating system (IMPORTANT).
If I call a member function inside the same file as the initial array [OUTSIDE THE TEMPLATING SYSTEM] (the file that I'm buffering in order to create the object in the first place), everything dies. This doesn't make sense to me because if I var_dump($BannerRotate), I get a full object. However, say in that content file I do $BannerRotate->printNoscript(), everything disappears and the object is never created. I then get a fatal error that I'm calling a member function of a non-object. THAT IS THE PROBLEM.
Here is what I'm doing within the templating system to buffer the content file (and create the object(s)):
ob_start();
include $core_page_content; // the content file (where initial array is)
if(isset($GLOBALS['config']['plugins'])){
foreach($GLOBALS['config']['plugins'] as $plugin => $ary){
$ins[] = $plugin;
}
}
$t = ob_get_contents();
ob_end_clean();
foreach($ins as $in){
$a = CURRENTSRV; // a,b,c,d are just making the eval statement more clean
$b = getcwd().'/';
$c = array();
foreach($GLOBALS['config']['plugins'][$in] as $key => $value){
$c[$key] = $value;
}
$d = $in;
eval("\$$in = new Ispweb_Plugindaemon(\"$a\",\"$b\",\$c,\"$d\");");
echo $$in;
}
include $core_page_content;
$page_content = ob_get_contents();
ob_end_clean();
Does anyone know why I can access the object UNLESS I make a call to one of its member functions while inside the same file?
What can I do?
P.S. I know the setup is not ideal. There's nothing I can do about that.
Thanks!
TL;DR I'm creating an object in file A, with a variable from file B. I buffer file B to get the parameters to feed to file A, create the object, print it into another buffer and include file B in that buffer as well. If file B has a function call to the presumably created object, I get a fatal error: call to member function of non-object.
Additional Notes:
File B:
$config['plugins']['BannerRotate'] = array(
'container' => 'abroadView',
'arrows' => true
);
// page content (XHTML)
File A:
ob_start();
$core_page_content = 'file_b';
include $core_page_content;
if(isset($config['plugins'])){
foreach($config['plugins'] as $plugin => $ary){
$ins[] = $plugin;
}
ob_end_clean();
foreach($ins as $in){
$$in = new Ispweb_Plugindaemon(CURRENTSRV,getcwd().'/',$config['plugins'][$in],$in);
}
include $core_page_content;
$page_content = ob_get_contents();
ob_end_clean();
// later on in the file
include 'top.htm';
include $page_content;
include 'bot.htm';
The problem was variable scope.
Related
I'm designing a simple templating system for a CMS in PHP which internally currently uses something like:
require_once 'templates/template1.php`;
to import the desired template.
I would like every content {{field123}} in this PHP file to be automatically converted into <?php echo $row['field123']; ?> before being passed into require_once and executed by PHP.
Is there a way to activate a preprocessor (I know that PHP is already named after preprocessor) that does this replacement {{anything}} -> <?php echo $row['anything']; ?> before executing the PHP code template1.php? If not, what's the usual way to do this?
Having PHP code in templates - especially code with potential side-effects - can get dirty real quick. I would recommend using static templates, treating them as strings instead of executing them, then parsing them for tokens, with your main application compiling them and handling output.
Here is a rudimentary implementation that parses variables into tokens, and also handles mapped function calls in your templates. First, "fetching" our template (for a simple example):
$tpl = 'This is a sample template file.
It can have values like {{foo}} and {{bar}}.
It can also invoke mapped functions:
{{func:hello}} or {{func:world}}.
Hello user {{username}}. Have a good day!';
Then, the template parser:
function parse_template(string $tpl, array $vars): string {
// Catch function tokens, handle if handler exists:
$tpl = preg_replace_callback('~{{func:([a-z_]+)}}~', function($match) {
$func = 'handler_' . $match[1];
if(function_exists($func)) {
return $func();
}
return "!!!What is: {$match[1]}!!!";
}, $tpl);
// Generate tokens for your variable keys;
$keys = array_map(fn($key) => '{{' . $key . '}}', array_keys($vars));
// Substitute tokens:
$tpl = str_replace($keys, $vars, $tpl);
return $tpl;
}
These are our handler functions, with handler_X matching {{func:X}}.
function handler_hello() {
return 'HELLO THERE';
}
function handler_world() {
return '#Current World Population: ' . mt_rand();
}
Then, here are the variables you'd like to parse in:
$vars = [
'foo' => 'Food',
'bar' => 'Barnacle',
'username' => 'Herbert'
];
Now let's parse our template:
$parsed = parse_template($tpl, $vars);
echo $parsed;
This results in:
This is a sample template file.
It can have values like Food and Barnacle.
It can also invoke mapped functions:
HELLO THERE or #Current World Population: 1477098027.
Hello user Herbert. Have a good day!
Job done. You really don't need a complicated templating engine for something like this. You could easily extend this to allow the handlers to receive arguments defined in the template tokens -- however I'll leave that for your homework part. This should do to demonstrate the concept.
As mentioned in a comment and in How do I capture PHP output into a variable?, the use of output buffering can work:
<?php
ob_start();
?>
Hello
{{field123}} and {{field4}}
World
<?php // or require_once 'template1.php'; ?>
<?php
$s = ob_get_clean();
$a = array('field123' => 'test', 'field4' => 'test2');
$s = preg_replace_callback('/{{(.*?)}}/', function ($m) use ($a) { return isset($a[$m[1]]) ? $a[$m[1]] : $m[0]; }, $s);
echo $s;
?>
// Output:
// Hello
// test and test2
// World
Here we also used a method similar to Replace with dynamic variable in preg_replace to do the replacement.
I would like to create a file based on template Twig (and one parameter).
My file generated correctly with replace variable with my data. But if I change data my controller symfony generated always the same file with the same content.
My varDumper content (the text/plain character represent file) is good. It's change always with change variable content. But the writing file always generate the same content...
PHP seems do not caching with fwrite ou file_put_contents function but my content never change. I also disable caching for twigEngine but same result.
Can you help me to writing file with good last content.
All code is in a controller symfony. I keep comment code for your understanding all my test :
public function createEntityAction()
{
$rootDir = $this->getParameter('kernel.root_dir');
$templateDir = $rootDir . '/../src/CmsBundle/Resources/views/Entity/templateFile/';
$filename = 'test.php';
$pathFile = $templateDir . $filename;
$twigEngine = $this->get('twig');
$twigEngine->setCache(false);
$twigEngine->disableAutoReload();
// $loader = new Twig_Loader_Filesystem($templateDir);
// $twig = new Twig_Environment($loader, [
// 'cache' => '/path/to/compilation_cache',
// 'cache' => false,
// ]);
$baseTemplate = $twigEngine->loadTemplate('#Cms/Entity/templateFile/baseEntity.html.twig');
$script = $baseTemplate->render(['slug' => 'product-333']);
\Symfony\Component\VarDumper\VarDumper::dump($script);
if (file_exists($pathFile))
{
clearstatcache(true);
$ret = unlink($pathFile);
\Symfony\Component\VarDumper\VarDumper::dump($ret);
}
$file = fopen($pathFile, 'w+');
fwrite($file, $script);
fclose($file);
// file_put_contents($pathFile, $script);
return $this->render('#Cms/Entity/create.html.twig', []);
}
The final file content is always the same if I change the "slug" variable.
It looks to me like you're missing a variable in your controller. There should be something like this createEntityAction($slug), then this line should be $baseTemplate->render(['slug' => $slug); Right now it is doing exactly what you are telling it, you have a hard coded string for the slug...
If you post your controller definition it would help (annotations or yaml)
I'm working on a project where I'm keeping all HTML content separate from the rest of the PHP code. Each instance where any HTML needs to be parsed for PHP variables is sent through a function call. Most these deal with dynamic data from the database.
A simple example of a template file:
<div id='{$data['id']}'>{$data['text']}</div>
The variables in the $data array are passed through a function call where the HTML snippet needs to be added to the output buffer:
$output .= $html->load_template('template_id', array('id' => 123, 'text' => 'Testing'));
The html::load_template() function simply locates the correct text file, and is supposed to load the variables and return the string as HTML. This is where I'm having issues:
public static function load($template, $data=array()) {
ob_start();
include ( TEMPLATE . $template .'.tpl' );
ob_flush();
}
I've tried using include() and file_get_contents(), but to no avail - I'm looking for a simple solution where I can use the {$data['var']} syntax, preferably retaining the template HTML as a simple variable, so it can then be added to the output.
I'm trying to avoid using eval().
Can someone give me some guidance?
I've done the same thing in the past, you can modify your below code like this:
public static function load($template, $data=array()) {
ob_start();
include ( TEMPLATE . $template .'.tpl' );
$getData = ob_get_clean();
preg_match_all("|{([^>].*)}|U", $getData, $getDataArr, PREG_SET_ORDER);
if (is_array($getDataArr) && count($getDataArr) > 0) {
foreach ($getDataArr as $php) {
if (strpos($php[1],'$') !== false) {
$getData= str_replace($php[0], (eval('return $'.str_replace('$', '', $php[1]).';')), $getData);
}
}
}
echo $getData;
}
I have a library in code igniter that looks like class MyClass($options = array())
The file is Myclass.php
I have a file (config/Myclass.php) that looks like
$Myclass = array(
'something' => 'value',
'another' => 'value'
);
Which I thought should pass the $Myclass array in when I initialize my class, but apparently not?
What do I need to do to fix it?
AH I found the answer,
The array inside your config file must be called $config.
The name of the file must also be a lower case representation of the library file name.
e.g
LIB FILE: Somelibrary.php
LIB CONTENTS: class Somelibrary($options = array()){...
CONF FILE: somelibrary.php
CONF CONTENTS: $config = array('something' => 'value');
The way this usually works is that you pass in an array of options you wish to override, or pass in nothing to use the defaults.
var myObject = new MyClass(); // default settings
var myObject = new MyClass(array('something' => 'value2')); // override only "something"
Honestly, I wouldn't create your own file in config without a good reason; instead, just put the defaults in your class definition, and then override in your constructor:
class MyClass {
var $default_options = array(
'something' => 'value',
'another' => 'value',
);
var $options = array();
function MyClass($override_options = array())
{
$this->options = array_merge($this->default_options, $override_options);
// your code here...
}
}
Fast-forward to 2013, someone is still having problems with this. So my situation was the same but slightly different, so I thought I'd try to save someone else some time.
I was naming my config file after my extended class, that was wrong. The Config file should always be form_validation.php, (this is because eventually it is handed to the CI_Form_validation class and that's what it expects)
Thanks to the folks who answered this question above, I realized that I had to pass the config to the parent class and using codeigniter v2.1.3 I did it as follows:
public function __construct( $config = array() )
{
parent::__construct($config);
}
I have a function that needs to include a file, however this functions is used in a place from 200 to 300 times, this is obviously causing efficiency issues, is there a way to optimize this inside the function? (i know there are many ways in which i can fix this but it will cause too much impact in the whole application)
I will just put a little example, this is not the whole function.
function getString(arrayName, strValue){
inclue('stringArrays.php');
return $$arrayName[strValue];
}
I tried using include_once, but that doesn't do the job either.
Thanks in advance.
You could use a static variable in the function to hold your values:
function getString($arrayName, $strValue){
static $string_arrays = array();
if (empty($string_arrays)) {
include('stringArrays.php');
$string_arrays = array_diff_key(get_defined_vars(), array(
'string_arrays' => true,
'arrayName' => true,
'strValue' => true,
));
}
return $string_arrays[$arrayName][$strValue];
}
Should only include the file once.
You could always add another parameter, perhaps a boolean, to tell the function whether or not to include it.
function getString(arrayName, strValue, includeFile)
{
if (includeFile)
{
inclue('stringArrays.php');
}
return $$arrayName[strValue];
}
You can try globalizing what's in stringArrays.php so you can check to see if that global variable is already set before including the file. Hard to tell without seeing what structure is in stringArrays.php.
If your function does nothing more than include a file you should be first evaluating whether that function should be called in the first place or make the function determine if an include is required. Basically don't blindly include a file if you truly don't need it included. include_once will incur a performance hit.
Install APC, eAccelerator, XCache or any other code accelerator so PHP doesn't need to retrieve the include file from disk every time it's called. Code accelerators save the file in shared memory. That will improve performance significantly.
Is there anything preventing you from wrapping your current "bunch" of arrays in an array, then passing that wrapper array into the function by reference? You can then do a single require/include outside of the function. Alternatively, you can wrap both the set of arrays and the function inside an object, again bringing you down to a single require/include.
If stringArrays.php is simply a collection of arrays, what about creating a stringHandler singleton that includes stringArrays.php within the constructor and maps the each array to a class property, then a simple method to get whichever you want from that class. Then your getString() function simply references a getter method in the stringHandler.
stringArrays.php
<?php
$abc = array('def' => 'Hello',
'ghi' => ' '
);
$jkl = array('mno' => 'World',
'pqr' => '.'
);
?>
stringHandler.php
<?php
class stringHandler
{
private static $instance;
private function __construct()
{
include('stringArrays.php');
foreach(get_defined_vars() as $key => $val) {
$this->{$key} = $val;
}
}
public static function singleton()
{
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
}
return self::$instance;
}
public function getStringFromArray($arrayName, $strValue)
{
return $this->{$arrayName}[$strValue];
}
}
function getString($arrayName, $strValue){
return stringHandler::singleton()->getStringFromArray($arrayName, $strValue);
}
echo getString('abc','def');
echo getString('abc','ghi');
echo getString('jkl','mno');
?>
Kludgy, but shouldn't be a big performance overhead.