1

I have a PHP script with some variables set in the global namespace:

$domain = 'example.com';
$hostname = 'www.' . $domain;

I am reading an external file into a string variable in my script using file_get_contents:

$file_contents = file_get_contents('external_file.tpl');

The external file (and the string $file_contents) contains placeholders which correspond to variable names.

127.0.0.1 {{domain}} {{hostname}}

I would like to have all the placeholders in the variable $file_contents replaced with their respective PHP variables already set. I want this to work generically (without hard-coding placeholder / variable names). I do not want to edit the file, just the contents of the string.

What's the easiest way to do this?

6
  • How would php know what placeholders to replace if you don't want to hard code it and it is in the global namespace? Commented Nov 23, 2015 at 21:57
  • @PeeHaa Any string between {{ and }} should be replaced with the corresponding variable. For example, {{some_var}} should be replaced with $some_var. Commented Nov 23, 2015 at 21:58
  • 1) parse the file 2) gets placeholders 3) look into the scope whether a variable exists 4) replace Commented Nov 23, 2015 at 21:59
  • And watch out for potential information leaking Commented Nov 23, 2015 at 22:00
  • @PeeHaa Exactly. But I'm a little overwhelmed with all the string replacement functions. I'm hoping someone can guide me in the right direction on which would be the best for this task. Also, this is going to need regex and I'm not that strong at regex. Commented Nov 23, 2015 at 22:01

6 Answers 6

2
  • Parse file for items between {{ and }}
  • Check to see if a global variable exists
  • Search and replace

Like so:

$matches = array();
preg_match('/\{\{([a-zA-Z0-9_]+)\}\}/', $file_contents, $matches);

foreach ($matches as $match)
{
    // You might want to check to make sure the variable is a string
    if (isset($GLOBALS[$match]))
    {
        str_replace('{{'.$match.'}}', $GLOBALS[$match], $file_contents);
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

I would probably use preg_replace_callback, but this is a good enough solution where you can also whitelist/blacklist variables.
@BugaDániel Yeah me too, I've upvoted this but added a preg_replace_callback solution to the answer
1

Disclaimer: I don't think it's a good idea to allow arbitrary variables to be used in whatever this template is going to be.

The function get_defined_vars() creates an array of all currently available variables in the form variable_name => value (THIS INCLUDES SUPERGLOBALS LIKE $_SERVER AND $_GET!)

The string translation function strtr(string $str, array $replace_pairs) can replace keys of an associative array with their values in a string.

Together they form a mighty but dangerous alliance. The following code will replace domain with the value of $domain and so on:

echo strtr(file_get_contents('external_file.tpl'), get_defined_vars());

Adding {{...}} is possible with a little extra effort:

$vars = get_defined_vars();
$replace = array();
foreach ($vars as $key => $value) {
    $replace['{{' . $key . '}}'] = $value;
}
echo strtr(file_get_contents('external_file.tpl'), $replace);

Use it responsibly! Remember not to trust any user input.

1 Comment

That is clever. Thanks for the answer.
1

You could use a preg_replace_callback to do this, you will need to put your replacements into an array though.

e.g

    $domain = 'example.com';



    $newString = preg_replace_callback(
        '/{{([^}}]+)}}/',
        function ($matches) {
            foreach ($matches as $match) {
                if (array_key_exists($match, $_GLOBALS))
                    return $replace[$match];
            }
        },
        $file_contents
    );

The matches returned for that particular regex would be an array

array [
    '{{hostname}}',
    'hostname',
    '{{domain}}',
    'domain'
]

Which is why we would do a check with array_key_exists

5 Comments

Thanks. Is there a way to do this without having to set up the array? I know that $matches[1] in the callback provides the name of the variable.
This doesn't work, but something along the lines of: function the_callback($matches) { return ${$matches[1]}; }.
@TomDworzanski you can't pass variables easily into the callback function so an array is best. You could set up the array elsewhere using some kind of algorithm though and call this through a function. I feel this is probably the best answer to solving the problem in a elegant way.
@TomDworzanski $_GLOBAL is an array anyway so it shouldn't be a problem to just pass the global into it
Yea exactly. Taking what @TonyDeStefano was doing with $GLOBALS and combining it with your approach, here's what I came up with: stackoverflow.com/a/33882176/636044
1

Alright guys, I took some bits and pieces from other answers and came up with what I think is the best solution for me:

$domain = 'example.com';
$hostname = 'www.' . $domain;
$file_contents = '127.0.0.1 {{domain}} {{hostname}}';

$result = preg_replace_callback(
    "/{{(.+?)}}/",
    function ($matches) {
        return $GLOBALS[$matches[1]];
    },
    $file_contents
);

This answer combines ideas from @TonyDeStefano, @dops, and @fschmengler. So please up-vote them. @BugaDániel also helped with his comments.

10 Comments

Wouldn't this only replace the first match, but the preg_replace_callback gets all matches?
@dops It seems to be replacing all matches for me.
I've edited my answer to include $_GLOBALS and that should replace every match in the file with one call. Am I being dim and missing something?
Fair enough, I'm not sure why :) but as long as it works
@BugaDániel Would this be better: /\{\{([a-zA-Z0-9_]+)\}\}/ (from @TonyDeStefano).
|
0

You could simply use str_replace to do this. Since you know what the place holders are going to be, try something like this:

str_replace(['{{domain}}','{{hostname}}'],[$domain, $hostname], $file_contents);

1 Comment

Thanks, but as I said in my question, I don't want the variable names to be hard-coded.
0

This will do the work for global variables

<?php

$domain = 'example.com';
$hostname = 'www.' . $domain;
$file_contents = "127.0.0.1 {{domain}} {{hostname}}";

foreach($GLOBALS as $key => $value){

    if(!is_string($value) && !is_numeric($value)){
        continue;
    }

    $file_contents = str_replace('{{'.$key.'}}', $value, $file_contents);
}

var_dump($file_contents);

Will output:

string '127.0.0.1 example.com www.example.com' (length=37)

Comments