8

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"
nickf
  • 537,072
  • 198
  • 649
  • 721

6 Answers6

11

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[1], $allVars))
    return $allVars[$match[1]];
  else
    return $match[0];
}
Blazemonger
  • 90,923
  • 26
  • 142
  • 180
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • 2
    actually, it's this (from the php manual): [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* – nickf Nov 12 '08 at 07:21
  • Yes, but is unnecessarily complicated. You are looking for white space or a word boundary. – Tomalak Nov 12 '08 at 07:28
  • 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 Nov 12 '08 at 07:32
  • But you spoke of PHP sytle variable names so I ruled out that possibility. :-) – Tomalak Nov 12 '08 at 07:50
  • 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 Nov 12 '08 at 08:25
  • 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 Nov 12 '08 at 08:33
  • how does code within find_replacements() see $allVars unless you global it? – Tom Haigh Nov 12 '08 at 11:21
  • 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 Nov 16 '08 at 08:59
5

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
  • 316,276
  • 54
  • 369
  • 333
  • Does addslashes() even out any possible syntax ambiguity? – Tomalak Nov 12 '08 at 08:44
  • @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 Nov 12 '08 at 09:16
  • Hm, and how does it react to "$name" when $name is not defined? – Tomalak Nov 12 '08 at 10:30
  • Same as normal php - it'll raise a notice (if you have notices on) and treat $name as an empty string. – Greg Nov 12 '08 at 11:32
2

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
  • 537,072
  • 198
  • 649
  • 721
2

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.
nickf
  • 537,072
  • 198
  • 649
  • 721
Zak
  • 24,947
  • 11
  • 38
  • 68
0

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
  • 24,875
  • 14
  • 66
  • 96
  • I believe so too, but the spec shows the variable is defined by a $ as the starting character, not enclosed by %'s – Xenph Yan Nov 12 '08 at 07:24
  • If you used $var instead of %var%, it wouldn't work if you had both $abc and $abcdef. – Tom Nov 12 '08 at 09:21
-1

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.

user37125
  • 209
  • 1
  • 3
  • would that work if there was something like this: "$pet $petty"? – nickf Nov 12 '08 at 23:38
  • 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. – user37125 Nov 13 '08 at 00:42