Parse a PHP string with PHP parser - php

I wanted to ask whether I'm doomed to use eval() or there may be a work around.
$str = 'Hello, $user.'; // $str is a string gotten from an external source
// Many lines later
$user = 'John Doe';
echo eval('return "'.$str.'";');
Not a big fan of eval, as probably many of you. Is there another way to parse a PHP string?

You can try with:
$str = 'Hello, $user.';
$data = array(
'user' => 'John Doe'
);
$output = preg_replace_callback('/\$(\w+)/', function($key) use ($data) {
return isset($data[$key[1]]) ? $data[$key[1]] : $key[1];
}, $str);
var_dump($output);
Output:
string 'Hello, John Doe.' (length=16)

You can try create_function. It doesn't execute just any piece of code, but wraps it in a function. Apart from that, it's not that different.
But if your goal is to replace variables alone, you might want to have a look at the str_replace function. That will work fine for a fixed set of variables. If you want to be more flexible, you can use preg_replace or preg_replace_callback, but note that a 'flexible' function is probably a function that allows you to use any variable. That also allows people to exploit that feature to read variables that they are not supposed to read.

Related

php url query nested array with no index

I'm working with a third party API that receives several parameters which must be encoded like this:
text[]=Hello%20World&text[]=How%20are%20you?&html[]=<p>Just%20fine,%20thank%20you</p>
As you can see this API can accept multiple parameters for text, and also for HTML (not in the sample call).
I have used http_build_query to correctly build a query string for other APIs
$params['text'][] = 'Hello World';
$params['text'][] = 'How are you?';
$params['html'][] = '<p>Just fine, thank you</p>';
$http_query = http_build_query($params);
The problem with this approach is that it will build a query string with the numeric index:
text[0]=Hello%20World&text[1]=How%20are%20you?&html[0]=<p>Just%20fine,%20thank%20you</p>
unfortunately the API I'm working with doesn't like the numeric index and fails.
Is there any php function/class-method that can help me build a query like this quickly?
Thank you
I don't know a standard way to do it (I think there is no such way), but here's an ugly solution:
Since [] is encoded by http_build_query, you may generate string with indices and then replace them.
preg_replace('/(%5B)\d+(%5D=)/i', '$1$2', http_build_query($params));
I very much agree with the answer by RiaD, but you might run into some problems with this code (sorry I can't just make this a comment due to lack of rep).
First off, as far as I know http_build_query returns an urlencode()'d string, which means you won't have [ and ] but instead you'll have %5B and %5D.
Second, PHP's PCRE engine recognizes the '[' character as the beginning of a character class and not just as a simple '[' (PCRE Meta Characters). This may end up replacing ALL digits from your request with '[]'.
You'll more likely want something like this:
preg_replace('/\%5B\d+\%5D/', '%5B%5D', http_build_query($params));
In this case, you'll need to escape the % characters because those also have a special meaning. Provided you have a string with the actual brackets instead of the escapes, try this:
preg_replace('/\[\d+\]/', '[]', $http_query);
There doesn't seem to be a way to do this with http_build_query. Sorry. On the docs page though, someone has this:
function cr_post($a,$b=0,$c=0){
if (!is_array($a)) return false;
foreach ((array)$a as $k=>$v){
if ($c) $k=$b."[]"; elseif (is_int($k)) $k=$b.$k;
if (is_array($v)||is_object($v)) {
$r[]=cr_post($v,$k,1);continue;
}
$r[]=urlencode($k)."=" .urlencode($v);
}
return implode("&",$r);
}
$params['text'][] = 'Hello World';
$params['text'][] = 'How are you?';
$params['html'][] = '<p>Just fine, thank you</p>';
$str = cr_post($params);
echo $str;
I haven't tested it. If it doesn't work then you're going to have to roll your own. Maybe you can publish a github gist so other people can use it!
Try this:
$params['text'][] = 'Hello World';
$params['text'][] = 'How are you?';
$params['html'][] = '<p>Just fine, thank you</p>';
foreach ($params as $key => $value) {
foreach ($value as $key2 => $value2) {
$http_query.= $key . "[]=" . $value2 . "&";
}
}
$http_query = substr($http_query, 0, strlen($http_query)-1); // remove the last '&'
$http_query = str_replace(" ", "%20", $http_query); // manually encode spaces
echo $http_query;

Parse variables within string

I'm storing some strings within a *.properties file. An example of a string is:
sendingFrom=Sending emails from {$oEmails->agentName}, to {$oEmails->customerCount} people.
My function takes the value from sendingFrom and then outputs that string on the page, however it doesn't automatically parse the {$oEmails->agentName} within. Is there a way, without manually parsing that, for me to get PHP to convert the variable from a string, into what it should be?
If you can modify your *.properties, here is a simple solution:
# in file.properties
sendingFrom = Sending emails from %s, to %s people.
And then replacing %s with the correct values, using sprintf:
// Get the sendingFrom value from file.properties to $sending_from, and:
$full_string = sprintf($sending_from, $oEmails->agentName, $oEmails->customerCount);
It allows you to separate the logic of your app (the variables, and how you get them) from your presentation (the actual string scheme, stored in file.properties).
Just an alternative.
$oEmails = new Emails('Me',4);
$str = 'sendingFrom=Sending emails from {$oEmails->agentName}, to {$oEmails->customerCount} people.';
// --------------
$arr = preg_split('~(\{.+?\})~',$str,-1,PREG_SPLIT_DELIM_CAPTURE);
for ($i = 1; $i < count($arr); $i+=2) {
$arr[$i] = eval('return '.substr($arr[$i],1,-1).';');
}
$str = implode('',$arr);
echo $str;
// sendingFrom=Sending emails from Me, to 4 people.
as others mentioned eval wouldn't be appropriate, I suggest a preg_replace or a preg_replace_callback if you need more flexibility.
preg_replace_callback('/\$(.+)/', function($m) {
// initialise the data variable from your object
return $data[$m[1]];
}, $subject);
Check this link out as well, it suggests the use of strstr How replace variable in string with value in php?
You can use Eval with all the usual security caviats
Something like.
$string = getStringFromFile('sendingFrom');
$FilledIn = eval($string);

improved sprintf for PHP

Does anyone know a better implementation of sprintf in PHP? I was looking for something like the string formatting we have in python:
print "Hello %(name)s. Your %(name)s has just been created!" % { 'name' : 'world' }
# prints::: Hello world. Your world has just been created!
This is pretty handy to avoid repeating the same variables without need, such as:
sprintf("Hello %s. Your %s has just been created!", 'world', 'world');
# prints::: Hello world. Your world has just been created!
I guess is fairly easy to build this on my own, but don't wanna reinvent the wheel, if you know what I mean... but I could not find (maybe wrong search keywords) any trace of this anywhere.
If anyone can help, I appreciate.
Cheers,
You can use positional (but not named) arguments to do this, for example
printf('Hello %1$s. Your %1$s has just been created!', 'world');
A word of caution here: you must use single quotes, otherwise the dollar signs will cause PHP to try to substitute $s with the value of this variable (which does not exist).
If you want named arguments then you will have to do this with a regular expression; for example, see How to replace placeholders with actual values?.
You can repeat the same placeholder with PHP's sprintf (though it might not look as nice):
$str = sprintf('%1$s %1$s', 'yay');
// str: 'yay yay'
You can use n$ right after the % in a placeholder, where n is the argument position (so %1$s refers to the first argument (as a string), %2$s refers to the second, etc.). As you can see above, when you use placeholders that are positionally-bound, you can repeat them within the string without duplicating arguments when you call sprintf.
The following code was stolen from a post by Salathe on TalkPHP.
$szAdjective = 'fluffy';
$szNoun = 'cat';
printf('Yesterday, I saw a %s. '.
'It was a %s %s! I have '.
'never seen a %s quite so %s.',
$szNoun,
$szAdjective,
$szNoun,
$szNoun,
$szAdjective);
printf('Yesterday, I saw a %1$s. '.
'It was a %2$s %1$s! I have '.
'never seen a %1$s quite so %2$s.',
$szNoun,
$szAdjective);
The above two expressions are equivalent and will both output
"Yesterday, I saw a cat. It was a fluffy cat! I have never seen a cat quite so fluffy."
I answered this very question in another post: vsprintf or sprintf with named arguments, or simple template parsing in PHP
But this has the same format youre looking for!
This is really the best way to go imho. No cryptic characters, just use the key names!
As taken from the php site:
http://www.php.net/manual/en/function.vsprintf.php
function dsprintf() {
$data = func_get_args(); // get all the arguments
$string = array_shift($data); // the string is the first one
if (is_array(func_get_arg(1))) { // if the second one is an array, use that
$data = func_get_arg(1);
}
$used_keys = array();
// get the matches, and feed them to our function
$string = preg_replace('/\%\((.*?)\)(.)/e',
'dsprintfMatch(\'$1\',\'$2\',\$data,$used_keys)',$string);
$data = array_diff_key($data,$used_keys); // diff the data with the used_keys
return vsprintf($string,$data); // yeah!
}
function dsprintfMatch($m1,$m2,&$data,&$used_keys) {
if (isset($data[$m1])) { // if the key is there
$str = $data[$m1];
$used_keys[$m1] = $m1; // dont unset it, it can be used multiple times
return sprintf("%".$m2,$str); // sprintf the string, so %s, or %d works like it should
} else {
return "%".$m2; // else, return a regular %s, or %d or whatever is used
}
}
$str = <<<HITHERE
Hello, %(firstName)s, I know your favorite PDA is the %(pda)s. You must have bought %(amount)s
HITHERE;
$dataArray = array(
'pda' => 'Newton 2100',
'firstName' => 'Steve',
'amount' => '200'
);
echo dsprintf($str, $dataArray);
// Hello, Steve, I know your favorite PDA is the Newton 2100. You must have bought 200
I've written a small component that allow you to make name substitutions in php strings. It's called StringTemplate.
With it you can get what you want with a code like this:
$engine = new StringTemplate\Engine;
$engine->render(
'"Hello {name}. Your {name} has just been created!"',
[
'name' => 'world',
]
);
//Prints "Hello world. Your world has just been created!"
Multidimensional array value are allowed too. Hope that can help.

PHP : simple string to array

I have strings like var=test;path=test.html
I want to convert these kind of strings like the array printed below :
Array
{
var => test
path => test.html,
}
I tried to use PHP's explode function for this task, but this is converting the string to associative array, and then i decided to convert that associate array to the one shown above.
But my own code isn't what i'm looking for, 'cause it contain 2 times PHP's explode function and some foreach loops, thus my own code will not stand in the department of performance for such a simple task.
Your help will be much appreciated, thanks.
Use parse_str for this:
http://www.php.net/manual/en/function.parse-str.php
You could explode by semicolon, then explode by =, and then use array_combine().
The solution is really not all that pretty, since it's quite verbose. I typed it out just in case, but you're probably better off with another method.
$s = "var=test;path=test.html";
$b = array_map(function($x){return explode("=", $x); }, explode(";", $s));
$c1 = array_map(function($x){return $x[0]; }, $b);
$c2 = array_map(function($x){return $x[1]; }, $b);
$result = array_combine($c1, $c2);
You could use preg_match_all to get an array of keys, and an array of values, then combine them into an associative array using array_combine:
$str = "var=test;path=test.html";
preg_match_all("/([^;=]+)=([^;=]+)/", $str, $matches);
$result = array_combine($matches[1], $matches[2]);
I just figured out how you can do that by using the parse_str php built-in function.
From the php.ini documentation:
; List of separator(s) used by PHP to parse input URLs into variables.
; PHP's default setting is "&".
; NOTE: Every character in this directive is considered as separator!
; http://php.net/arg-separator.input
; Example:
;arg_separator.input = ";&"
So, if you do this:
ini_set('arg_separator.input', ';&');
The parse_str should explode query arguments both on ; and &. This shouldn't affect in any way the filling of $_GET and $_POST since they are loaded before your code execution. If you want to be sure you don't affect the behavior of any other function calling parse_str, you could use a function like this:
function my_parse_str($str, &$arr) {
$orig = ini_get('arg_separator.input');
ini_set('arg_separator.input', ';');
parse_str($str, $arr);
ini_set('arg_separator.input', $orig);
}
Two advantages over exploding on & and then on =:
Maximum execution speed since the parse_str() function is built-in
parse_str() also considers recursive splitting: a=1&a=2 -> array('a'=>array('1', '2')); or 'a[one]=1&a[two]=2' -> array('a'=>array('one'=>'1', 'two'=>'2')).
Update - performance benchmarking
I just run a test to compare the plain-php splitting vs parse_str(), on an array of 10000 query strings each made of 500 arguments. The my_parse_str() above took ~0.952 seconds, while the pure-php one (parseQueryString()) took ~4.25 seconds.
It would require a larger set of data to test exactly how much it is faster, but it's pretty clear which one wins :) (if you want the test data + scripts, I'll upload them somewhere, since the data file is 125MB).
How about str_replace?
$text = "var=test;path=test.html";
$text = str_replace(';',"\n\t", $text);
$text = str_replace('='," => ", $text);
echo <<<END
Array
{
\t$text
}
END;
You'll need to do some extra work to get the proper formatting, though

How to replace multiple %tags% in a string with PHP

What is the best way of replacing a set of short tags in a PHP string, example:
$return = "Hello %name%, thank you for your interest in the %product_name%. %representative_name% will contact you shortly!";
Where I would define that %name% is a certain string, from an array or an object such as:
$object->name;
$object->product_name;
etc..
I know I could run str_replace multiple times on a string, but I was wondering if there is a better way of doing that.
Thanks.
str_replace() seems an ideal option if you know the placeholders you intend to replace. This need be run just once not multiple times.
$input = "Hello %name%, thank you for your interest in the %product_name%. %representative_name% will contact you shortly!";
$output = str_replace(
array('%name%', '%product_name%', '%representative_name%'),
array($name, $productName, $representativeName),
$input
);
This class should do it:
<?php
class MyReplacer{
function __construct($arr=array()){
$this->arr=$arr;
}
private function replaceCallback($m){
return isset($this->arr[$m[1]])?$this->arr[$m[1]]:'';
}
function get($s){
return preg_replace_callback('/%(.*?)%/',array(&$this,'replaceCallback'),$s);
}
}
$rep= new MyReplacer(array(
"name"=>"john",
"age"=>"25"
));
$rep->arr['more']='!!!!!';
echo $rep->get('Hello, %name%(%age%) %notset% %more%');
The simplest and shortest option is preg_replace with the 'e' switch
$obj = (object) array(
'foo' => 'FOO',
'bar' => 'BAR',
'baz' => 'BAZ',
);
$str = "Hello %foo% and %bar% and %baz%";
echo preg_replace('~%(\w+)%~e', '$obj->$1', $str);
From the PHP manual for str_replace:
If search and replace are arrays, then
str_replace() takes a value from each
array and uses them to do search and
replace on subject. If replace has
fewer values than search, then an
empty string is used for the rest of
replacement values. If search is an
array and replace is a string, then
this replacement string is used for
every value of search. The converse
would not make sense, though.
http://php.net/manual/en/function.str-replace.php

Categories