PHP preg_match template array as a second argument not working - php

I found this neat code from: https://www.webmasterworld.com/php/3444822.htm
First example seems to work well:
$firstname = "Eric";
$lastname = "Johnsson";
echo preg_replace("/\{([^\{]{1,100}?)\}/e", "$$1", "{lastname}, {firstname}");
But when I try to use second array version, it gives me index and other errors what ever combinations I try:
$values = array('firstname'=>'Eric', 'lastname'=>'Johnsson');
echo preg_replace("/\{([^\{]{1,100}?)\}/e", "$values[$1]", "{lastname}, {firstname}");
In PHP 5.5x it "should" work. PHP 7.x -> needs to have second argument a function, not accepting -e argument on regex.
Does anyone know working solution to second version? I rather not use export function to extract variables to the working scope.

You need to use preg_replace_callback as in the code below:
$values = array('firstname'=>'Eric', 'lastname'=>'Johnsson');
echo preg_replace_callback("/{([^{}]*)}/", function($m) use ($values) {
return !empty($values[$m[1]]) ? $values[$m[1]] : $m[0];
}, "{lastname}, {firstname} {somestring}");
See the PHP demo
Note that to pass the $values to the anonymous callback function, you need to pass it within use argument. With !empty($values[$m[1]]) you can check if your array contains the necessary key-value, and if yes, replace with it, and if not, just restore the match with the current match value, $m[0].
Note you do not need to escape { and } in this pattern, and you may just use {([^{}]*)} to match any number of chars other than { and } between { and }. If you are only interested in the substrings containing word chars, a {(\w+)} pattern could be more suitable.

Related

Binding values to a JSON string using PHP

Using PHP, I'd like to bind a value to a JSON string similar to how it is done when preparing an SQL statement for a database using a question mark. For example, I would like do something like the following:
$v1 = 'v1';
$k2 = 'k2';
$result = json_bind('[{"k1": ?}, {?: "v2"}]', $v1, $k2);
echo $result; // [{"k1": "v1"}, {"k2": "v2"}]
I don't want to just do a pure string replace as this technique doesn't escape/quote the values properly and it can't format values like arrays and objects properly.
I don't want to just create an array (or an object) first, assign my params, and use json_encode because this gets messy since PHP's syntax differs from JSON's syntax and I need something more dynamic because I may not always know the structure of the JSON.
Is there already a library or function that can do this very thing?
Is there already a library or function that can do this very thing?
No.
It's sort of doable with a simple regex "tokenizer" - by skipping strings mostly.
#-- Replace `?` placeholders
function json_bind($template, ...$vars) {
return preg_replace_callback(
'/
[{}[\],:] \K | # meta chars
(true|false|null) \K | # literals
[-+\d.e]+ \K | # numbers
"([^"\\\\]+|\\\\.)*" \K | # strings
([?]) # placeholder (do not skip)
/x',
function ($m) use (& $vars) {
if ($m[0] == "?") {
return json_encode(array_shift($vars));
}
},
$template
);
}
While that's not a very complete/clean regex, it should skip over most standard JSON structures and most strings (see Regex to validate JSON for better regexps). Which is how you can find literal ? placeholders.
Notably that doesn't take care of semantically invalid templates, like array params for JSON keys e.g.
How can you not know the JSON structure? What than is the use of passing values to a function in order to bind values to a string, if you can not distinguish a question mark being a key or a value?
In a sence you are saying I do know what I want to bind and I pass it in an arbitrary way to a function, although I have no clue as to the structure of the source JSON string. Thus I have no clue as to the outcome being correct or not.
Why would anyone want to have such a thing?
You simply have to go with the answer of #Bart Friederichs. This is the only proper answer to your question.

evaluate string with array php

I have a string like
"subscription link :%list:subscription%
unsubscription link :%list:unsubscription%
------- etc"
AND
I have an array like
$variables['list']['subscription']='example.com/sub';
$variables['list']['unsubscription']='example.com/unsub';
----------etc.
I need to replace %list:subscription% with $variables['list']['subscription'],And so on
here list is first index and subscription is the second index from $variable
.Is possible to use eval() for this? I don't have any idea to do this,please help me
Str replace should work for most cases:
foreach($variables as $key_l1 => $value_l1)
foreach($value_l1 as $key_l2 => $value_l2)
$string = str_replace('%'.$key_l1.':'.$key_l2.'%', $value_l2, $string);
Eval forks a new PHP process which is resource intensive -- so unless you've got some serious work cut out for eval it's going to slow you down.
Besides the speed issue, evals can also be exploited if the origin of the code comes from the public users.
You could write the string to a file, enclosing the string in a function definition within the file, and give the file a .php extension.
Then you include the php file in your current module and call the function which will return the array.
I would use regular expression and do it like that:
$stringWithLinks = "";
$variables = array();
// your link pattern, in this case
// the i in the end makes it case insensitive
$pattern = '/%([a-z]+):([a-z]+)%/i';
$matches = array();
// http://cz2.php.net/manual/en/function.preg-replace-callback.php
$stringWithReplacedMarkers = preg_replace_callback(
$pattern,
function($mathces) {
// important fact: $matches[0] contains the whole matched string
return $variables[$mathces[1]][$mathces[2]];
},
$stringWithLinks);
You can obviously write the pattern right inside, I simply want to make it clearer. Check PHP manual for more regular expression possibilities. The method I used is here:
http://cz2.php.net/manual/en/function.preg-replace-callback.php

Using of PHP constants in foreach loop

I need to use PHP contants in foreach loop:
define('WEBS', 'http://google.com, http://yahoo.com');
foreach (WEBS as $a) {
//do something
}
However I managed to do that by following code, although it works fine but aptana editor shows syntax error. Please guide me how to do that in the correct manner.
foreach (get_defined_constants(true)['user']['WEBS'] as $w) {
//do something
}
You have to explode the values first
foreach (explode(', ', WEBS) as $url) { ...
explode() will break a string into an array so that you can iterate through it
Alternatively, you could even use preg_split.
foreach(preg_split('/,\s*/', WEBS) as $url) { ...
preg_split() allows you to split your string based on a regular expression. It returns an array. As an example, using this regex, the space following the comma is optional.
# a string like this
foo.com, hello.com,world.com, test.com
# would still split properly to
[
'foo.com',
'hello.com',
'world.com',
'test.com'
]
Regex methods are not always necessary. But I thought I'd show you that a little more control is available when it's necessary.
Aptana is showing you an error because you can't use [] after a func() prior to PHP 5.4. To get around that, you can do things like:
$constants = get_defined_constants(true);
$constants['user']['WEBS'];
But in this case WEBS should be just fine. The only issue you were having is that you needed to convert the string to an array first.
Looks like misuse of constants.
It seems you need a regular variable of array type.
$webs = array('http://google.com', 'http://yahoo.com');
foreach ($webs as $a) {
//do something
}
If you still thinks that constants is what you need - better ask another question, explaining why so, and be told of what you're doing wrong

Is it possible to render the content of a file without using eval in PHP?

I'm trying to create a simple framework in PHP which will include a file (index.bel) and render the variables within the file. For instance, the index.bel could contain the following:
<h1>{$variable_name}</h1>
How would I achieve this without using eval or demanding the users of the framework to type index.bel like this:
$index = "<h1>{$variable_name}</h1>";
In other words: Is it possible to render the content of a file without using eval? A working solution for my problem is this:
index.php:
<?php
$variable_name = 'Welcome!';
eval ('print "'.file_get_contents ("index.bel").'";');
index.bel:
<h1>{$variable_name}</h1>
I know many have recommended you to add template engine, but if you want to create your own, easiest way in your case is use str_replace:
$index = file_get_contents ("index.bel");
$replace_from = array ('$variable_a', '$variable_b');
$replace_to = array ($var_a_value, $var_b_value);
$index = str_replace($replace_from,$replace_to,$index);
Now that is for simple variable replace, but you soon want more tags, more functionality, and one way to do things like these are using preg_replace_callback. You might want to take a look at it, as it will eventually make possible to replace variables, include other files {include:file.bel}, replace text like {img:foo.png}.
EDIT: reading more your comments, you are on your way to create own framework. Take a look at preg_replace_callback as it gives you more ways to handle things.
Very simple example here:
...
$index = preg_replace_callback ('/{([^}]+)}>/i', 'preg_callback', $index);
...
function preg_callback($matches) {
var_dump($matches);
$s = preg_split("/:/",$matches[1]); // string matched split by :
$f = 'func_'.strtolower($s[0]); // all functions are like func_img,func_include, ...
$ret = $f($s); // call function with all split parameters
return $ret;
}
function func_img($s) {
return '<img src="'.$s[1].'" />';
}
From here you can improve this (many ways), for example dividing all functionalities to classes, if you want.
Yes, this is possible, but why are you making your own framework? The code you provided clearly looks like Smarty Template. You could try to look how they did it.
A possible way to run those code is splitting them into pieces. You split on the dollar sign and the next symbol which is not an underscore, a letter or an number. Once you did that. You could parse it into a variable.
$var = 'variable_name'; // Split it first
echo $$var; // Get the given variable
Did you mean something like this?

How To Get The Unique Name Count With PHP?

Let's say I have text file Data.txt with:
26||jim||1990
31||Tanya||1942
19||Bruce||1612
8||Jim||1994
12||Brian||1988
56||Susan||2201
and it keeps going.
It has many different names in column 2.
Please tell me, how do I get the count of unique names, and how many times each name appears in the file using PHP?
I have tried:
$counts = array_count_values($item[1]);
echo $counts;
after exploding ||, but it does not work.
The result should be like:
jim-2,
tanya-1,
and so on.
Thanks for any help...
Read in each line, explode using the delimiter (in this case ||), and add it to an array if it does not already exist. If it does, increment the count.
I won't write the code for you, but here a few pointers:
fread reads in a line
explode will split the line based on a delimiter
use in_array to check if the name has been found before, and to determine whether you need to add the name to the array or just increment the count.
Edit:
Following Jon's advice, you can make it even easier for you.
Read in line-by-line, explode by delimiter and dump all the names into an array (don't worry about checking if it already exists). After you're done, use array_count_values to get every unique name and its frequency.
Here's my take on this:
Use file to read the data file, producing an array where each element corresponds to a line in the input.
Use array_filter with trim as the filter function to remove blank lines from this array. This takes advantage that trim returns a string having removed whitespace from both ends of its argument, leaving the empty string if the argument was all whitespace to begin with. The empty string converts to boolean false -- thus making array_filter disregard lines that are all whitespace.
Use array_map with a callback that involves calling explode to split each array element (line of text) into three parts and returning the second of these. This will produce an array where each element is just a name.
Use array_map again with strtoupper as the callback to convert all names to uppercase so that "jim" and "JIM" will count as the same in the next step.
Finally, use array_count_values to get the count of occurrences for each name.
Code, taking things slowly:
function extract_name($line) {
// The -1 parameter (available as of PHP 5.1.0) makes explode return all elements
// but the last one. We want to do this so that the element we are interested in
// (the second) is actually the last in the returned array, enabling us to pull it
// out with end(). This might seem strange here, but see below.
$parts = explode('||', $line, -1);
return end($parts);
}
$lines = file('data.txt'); // #1
$lines = array_filter($lines, 'trim'); // #2
$names = array_map('extract_name', $lines); // #3
$names = array_map('strtoupper', $names); // #4
$counts = array_count_values($names); // #5
print_r($counts); // to see the results
There is a reason I chose to do this in steps where each steps involves a function call on the result of the previous step -- that it's actually possible to do it in just one line:
$counts = array_count_values(
array_map(function($line){return strtoupper(end(explode('||', $line, -1)));},
array_filter(file('data.txt'), 'trim')));
print_r($counts);
See it in action.
I should mention that this might not be the "best" way to solve the problem in the sense that if your input file is huge (in the ballpark of a few million lines) this approach will consume a lot of memory because it's reading all the input in memory at once. However, it's certainly convenient and unless you know that the input is going to be that large there's no point in making life harder.
Note: Senior-level PHP developers might have noticed that I 'm violating strict standards here by feeding the result of explode to a function that accepts its argument by reference. That's valid criticism, but in my defense I am trying to keep the code as short as possible. In production it would be indeed better to use $a = explode(...); return $a[1]; although there will be no difference as regards the result.
While I do feel that this website's purpose is to answer questions and not do homework assignments, I don't acknowledge the assumption that you are doing your homework, since that fact has not been provided. I personally learned how to program by example. We all learn our own ways, so here is what I would do if I were to attempt to answer your question as accurately as possible, based on the information you have provided.
<?php
$unique_name_count = 0;
$names = array();
$filename = 'Data.txt';
$pointer = fopen($filename,'r');
$contents = fread($pointer,filesize($filename));
fclose($pointer);
$lines = explode("\n",$contents);
foreach($lines as $line)
{
$split_str = explode('|',$line);
if(isset($split_str[2]))
{
$name = strtolower($split_str[2]);
if(!in_array($name,$names))
{
$names[] = $name;
$unique_name_count++;
}
}
}
echo $unique_name_count.' unique name'.(count($unique_name_count) == 1 ? '' : 's').' found in '.$filename."\n";
?>

Categories