views:

1113

answers:

9

What's the best way to take some plain text (not PHP code) which contains PHP-style variables, and then substitute in the value of the variable. This is kinda hard to describe, so here's an example.

// -- myFile.txt --
Mary had a little $pet.

// -- parser.php --
$pet = "lamb";
// open myFile.txt and transform it such that...
$newContents = "Mary had a little lamb.";

I've been considering using a regex or perhaps eval(), though I'm not sure which would be easiest. This script is only going to be running locally, so any worries regarding security issues and eval() do not apply (i think?).

I'll also just add that I can get all the necessary variables into an array by using get_defined_vars():

$allVars = get_defined_vars();
echo $pet;             // "lamb"
echo $allVars['pet'];  // "lamb"
A: 

Here's what I've just come up with, but I'd still be interested to know if there's a better way. Cheers.

$allVars = get_defined_vars();
$file = file_get_contents('myFile.txt');

foreach ($allVars as $var => $val) {
    $file = preg_replace("@\\$" . $var . "([^a-zA-Z_0-9\x7f-\xff]|$)@", $val . "\\1", $file);
}
nickf
+4  A: 

Regex would be easy enough. And it would not care about things that eval() would consider a syntax error.

Here's the pattern to find PHP style variable names.

\$\w+

I would probably take this general pattern and use a PHP array to look up each match I've found (using (preg_replace_callback()). That way the regex needs to be applied only once, which is faster on the long run.

$allVars = get_defined_vars();
$file = file_get_contents('myFile.txt');

// unsure if you have to use single or double backslashes here for PHP to understand
preg_replace_callback ("\$\w+", "find_replacements", $file);

// replace callback function
function find_replacements($match)
{
  global $allVars;
  if (array_key_exists($match[0], $allVars)
    return $allVars[$match[0]];
  else
    return $match[0];
}
Tomalak
actually, it's this (from the php manual): [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*
nickf
...with a $ at the start, of course.
nickf
Yes, but is unnecessarily complicated. You are looking for white space or a word boundary.
Tomalak
i know it's a massive edge case and that I'm just nitpicking now, but \w won't match accented characters like çäÄ, which are valid characters.
nickf
But you spoke of PHP sytle variable names so I ruled out that possibility. :-)
Tomalak
oh, they're valid php variable characters. See the first code example on this page: http://au2.php.net/manual/en/language.variables.php
nickf
So I got it right initially, or what is that supposed to mean? :-) As with all regex - know your data, use the appropriate pattern. As I don't know your data, I figured \w would be close enough. But the exact pattern was not subject of the question, was it?
Tomalak
how does code within find_replacements() see $allVars unless you global it?
Tom Haigh
You are right, that's missing. PHP sure is a convoluted language...
Tomalak
Use \b for work boundaries, like: /$\w\b/
Preston
No need to. \w+ will already go to the end of the word. Naturally a word boundary follows, so you don't have to mention it specifically.
Tomalak
A: 

Depending on the situation, str_replace might do the trick.

Example:

// -- myFile.txt --
Mary had a little %pet%.

// -- parser.php --
$pet = "lamb";
$fileName = myFile.txt

$currentContents = file_get_contents($fileName);

$newContents = str_replace('%pet%', $pet, $currentContents);

// $newContents == 'Mary had a little lamb.'

When you look at str_replace note that search and replace parameters can take arrays of values to search for and replace.

Noah Goodrich
I believe so too, but the spec shows the variable is defined by a $ as the starting character, not enclosed by %'s
Xenph Yan
If you used $var instead of %var%, it wouldn't work if you had both $abc and $abcdef.
Tom
+1  A: 

It seems like you are trying to implement a simple native PHP template engine. A long time ago I found an interesting article about this, called Beyond The Template Engine. You may find it inspiring.

Adam Byrtek
+2  A: 

If it's from a trusted source you can use (dramatic pause) eval() (gasps of horror from the audience).

$text = 'this is a $test'; // single quotes to simulate getting it from a file
$test = 'banana';
$text = eval('return "' . addslashes($text) . '";');
echo $text; // this is a banana
Greg
Does addslashes() even out any possible syntax ambiguity?
Tomalak
@Tomalak: Yes. From the manual, it replaces single quotes, double quotes and NULL characters. While you don't have those, you're still inside the string. This is a beautiful, simple example of using eval; I've done stuff like this before, when I needed to hack something for one-time in-house use.UP!
Tom
Hm, and how does it react to "$name" when $name is not defined?
Tomalak
Same as normal php - it'll raise a notice (if you have notices on) and treat $name as an empty string.
Greg
hey that's not bad!
nickf
marked as accepted for the pure simplicity of it. thanks!
nickf
A: 

Does it have to be $pet? Could it be <?= $pet ?> instead? Because if so, just use include. This is the whole idea of php as a templating engine.

//myFile.txt
Mary had a little <?= $pet ?>.

//parser.php

$pet = "lamb";
ob_start();
include("myFile.txt");
$contents = ob_end_clean();

echo $contents;

This will echo out :

Mary had a little lamb.
Zak
A: 

You could use strtr:

$text = file_get_contents('/path/to/myFile.txt'); // "Mary had a little $pet."
$allVars = get_defined_vars(); // array('pet' => 'lamb');
$translate = array();

foreach ($allVars as $key => $value) {
    $translate['$' . $key] = $value; // prepend '$' to vars to match text
}

// translate is now array('$pet' => 'lamb');

$text = strtr($text, $translate);

echo $text; // "Mary had a little lamb."

You probably want to do the prepending in get_defined_vars(), so you don't loop the variables twice. Or better yet, just make sure whatever keys you assign initially match the identifier you use in myFile.txt.

would that work if there was something like this: "$pet $petty"?
nickf
That's not how strtr works.
orlandu63
It won't work with "$pet $petty". But if you also include a postfix (like "$pet$ $petty$" or something similar) it will.This is not how strtr works? Try it, it does.
A: 

Eval is best method

A: 

Eval is best method