I have a php string containing the serialization of a javascript object :
$string = '{fu:"bar",baz:["bat"]}';
The actual string is far more complicated, of course, but still well-formed javascript. This is not standard JSON, so json_decode fails. Do you know any php library that would parse this string and return a php associative array ?
This sounded like a fun challenge, so I coded up a tiny parser :D
class JsParserException extends Exception {}
function parse_jsobj($str, &$data) {
$str = trim($str);
if(strlen($str) < 1) return;
if($str{0} != '{') {
throw new JsParserException('The given string is not a JS object');
}
$str = substr($str, 1);
/* While we have data, and it's not the end of this dict (the comma is needed for nested dicts) */
while(strlen($str) && $str{0} != '}' && $str{0} != ',') {
/* find the key */
if($str{0} == "'" || $str{0} == '"') {
/* quoted key */
list($str, $key) = parse_jsdata($str, ':');
} else {
$match = null;
/* unquoted key */
if(!preg_match('/^\s*[a-zA-z_][a-zA-Z_\d]*\s*:/', $str, $match)) {
throw new JsParserException('Invalid key ("'.$str.'")');
}
$key = $match[0];
$str = substr($str, strlen($key));
$key = trim(substr($key, 0, -1)); /* discard the ':' */
}
list($str, $data[$key]) = parse_jsdata($str, '}');
}
"Finshed dict. Str: '$str'\n";
return substr($str, 1);
}
function comma_or_term_pos($str, $term) {
$cpos = strpos($str, ',');
$tpos = strpos($str, $term);
if($cpos === false && $tpos === false) {
throw new JsParserException('unterminated dict or array');
} else if($cpos === false) {
return $tpos;
} else if($tpos === false) {
return $cpos;
}
return min($tpos, $cpos);
}
function parse_jsdata($str, $term="}") {
$str = trim($str);
if(is_numeric($str{0}."0")) {
/* a number (int or float) */
$newpos = comma_or_term_pos($str, $term);
$num = trim(substr($str, 0, $newpos));
$str = substr($str, $newpos+1); /* discard num and comma */
if(!is_numeric($num)) {
throw new JsParserException('OOPSIE while parsing number: "'.$num.'"');
}
return array(trim($str), $num+0);
} else if($str{0} == '"' || $str{0} == "'") {
/* string */
$q = $str{0};
$offset = 1;
do {
$pos = strpos($str, $q, $offset);
$offset = $pos;
} while($str{$pos-1} == '\\'); /* find un-escaped quote */
$data = substr($str, 1, $pos-1);
$str = substr($str, $pos);
$pos = comma_or_term_pos($str, $term);
$str = substr($str, $pos+1);
return array(trim($str), $data);
} else if($str{0} == '{') {
/* dict */
$data = array();
$str = parse_jsobj($str, $data);
return array($str, $data);
} else if($str{0} == '[') {
/* array */
$arr = array();
$str = substr($str, 1);
while(strlen($str) && $str{0} != $term && $str{0} != ',') {
$val = null;
list($str, $val) = parse_jsdata($str, ']');
$arr[] = $val;
$str = trim($str);
}
$str = trim(substr($str, 1));
return array($str, $arr);
} else if(stripos($str, 'true') === 0) {
/* true */
$pos = comma_or_term_pos($str, $term);
$str = substr($str, $pos+1); /* discard terminator */
return array(trim($str), true);
} else if(stripos($str, 'false') === 0) {
/* false */
$pos = comma_or_term_pos($str, $term);
$str = substr($str, $pos+1); /* discard terminator */
return array(trim($str), false);
} else if(stripos($str, 'null') === 0) {
/* null */
$pos = comma_or_term_pos($str, $term);
$str = substr($str, $pos+1); /* discard terminator */
return array(trim($str), null);
} else if(strpos($str, 'undefined') === 0) {
/* null */
$pos = comma_or_term_pos($str, $term);
$str = substr($str, $pos+1); /* discard terminator */
return array(trim($str), null);
} else {
throw new JsParserException('Cannot figure out how to parse "'.$str.'" (term is '.$term.')');
}
}
Usage:
$data = '{fu:"bar",baz:["bat"]}';
$parsed = array();
parse_jsobj($data, $parsed);
var_export($parsed);
Gives:
array (
'fu' => 'bar',
'baz' =>
array (
0 => 'bat',
),
)
Tested with these strings:
'{fu:"bar",baz:["bat"]}',
'{rec:{rec:{rec:false}}}',
'{foo:[1,2,[3,4]]}',
'{fu:{fu:"bar"},bar:{fu:"bar"}}',
'{"quoted key":[1,2,3]}',
'{und:undefined,"baz":[1,2,"3"]}',
'{arr:["a","b"],"baz":"foo","gar":{"faz":false,t:"2"},f:false}',
Pear Services_JSON will parse that string (tested version 1.31). But given that that is a JSON parser and that this isn't valid JSON you have no guarantee that future versions will still work.
I found out that the Yii-framework's CJSON::decode() function handles Javascript objects as well.
If you're not using Yii, you should be able to just use the source code
thank luttkens
the CJON::decode() class of the Yii-framework works perfectly !
require_once ($_SERVER['DOCUMENT_ROOT']."/phplib/CJSON.php");
$json = new CJSON();
$data = $json->decode('{ url : "/jslib/maps/marker/marker_red.png", height : 34, width : 20, anchorIcon : [5,25.5], anchorText : [0,2], }', true);
print_r( $data );
result :
Array
(
[url] => /jslib/maps/marker/marker_red.png
[height] => 34
[width] => 20
[anchorIcon] => Array
(
[0] => 5
[1] => 25.5
)
[anchorText] => Array
(
[0] => 0
[1] => 2
)
)
What about that library?
http://timwhitlock.info/tag/jparser/
I haven't tried it yet.
Related
Python has a feature called template strings.
>>> from string import Template
>>> s = Template('$who likes $what')
>>> s.substitute(who='tim', what='kung pao')
'tim likes kung pao'
I know that PHP allows you to write:
"Hello $person"
and have $person substituted, but the templates can be reused in various sections of the code?
You can use template strings like this:
$name = "Maria";
$info["last_name"] = "Warner";
echo "Hello {$name} {$info["last_name"]}";
This will echo Hello Maria Warner.
You could also use strtr:
$template = '$who likes $what';
$vars = array(
'$who' => 'tim',
'$what' => 'kung pao',
);
echo strtr($template, $vars);
Outputs:
tim likes kung pao
I think there are a bunch of ways to do this... but this comes to mind.
$search = array('%who%', '%what_id%');
$replace = array('tim', 'kung pao');
$conference_target = str_replace(
$search,
$replace,
"%who% likes %what%"
);
Ha, we even had one in our framework using vsprintf:
class Helper_StringFormat {
public static function sprintf($format, array $args = array()) {
$arg_nums = array_slice(array_flip(array_keys(array(0 => 0) + $args)), 1);
for ($pos = 0; preg_match('/(?<=%)\(([a-zA-Z_]\w*)\)/', $format, $match, PREG_OFFSET_CAPTURE, $pos);) {
$arg_pos = $match[0][2];
$arg_len = strlen($match[0][0]);
$arg_key = $match[1][0];
if (! array_key_exists($arg_key, $arg_nums)) {
user_error("sprintfn(): Missing argument '${arg_key}'", E_USER_WARNING);
return false;
}
$format = substr_replace($format, $replace = $arg_nums[$arg_key] . '$', $arg_pos, $arg_len);
$pos = $arg_pos + strlen($replace);
}
return vsprintf($format, array_values($args));
}
}
Which looks like it came from the sprintf page
This allows for calls like:
sprintfn('second: %(second)s ; first: %(first)s', array(
'first' => '1st',
'second'=> '2nd'
));
UPDATE
Here is an update to do what you want... not fully tested though
class Helper_StringFormat {
public static function sprintf($format, array $args = array()) {
$arg_nums = array_slice(array_flip(array_keys(array(0 => 0) + $args)), 1);
for ($pos = 0; preg_match('/(?<=%)\(([a-zA-Z_][\w\s]*)\)/', $format, $match, PREG_OFFSET_CAPTURE, $pos);) {
$arg_pos = $match[0][1];
$arg_len = strlen($match[0][0]);
$arg_key = $match[1][0];
if (! array_key_exists($arg_key, $arg_nums)) {
user_error("sprintfn(): Missing argument '${arg_key}'", E_USER_WARNING);
return false;
}
$format = substr_replace($format, $replace = $arg_nums[$arg_key] . '$', $arg_pos, $arg_len);
$pos = $arg_pos + strlen($replace); // skip to end of replacement for next iteration
}
return vsprintf($format, array_values($args));
}
}
$str = "%(my var)s now work with a slight %(my var2)s";
$repl = array("my var" => "Spaces", "my var2" => "modification.");
echo Helper_StringFormat::sprintf($str, $repl);
OUTPUT
Spaces now work with a slight modification.
Another more simple approach would be this:
$s = function ($vars) {
extract($vars);
return "$who likes $what";
};
echo $s(['who' => 'Tim', 'what' => 'King Pao']); // Tim likes King Pao
And yes, PHPStorm will complain...
I personally most like sprintf (or vsprintf, for an array of arguments). It places them in the intended order, coerces types as needed, and has a lot more advanced features available.
Example:
$var = sprintf("%s costs %.2f dollars", "Cookies", 1.1);
This will result in the value Cookies cost 1.10 dollars.
There's an entire family of printf functions for different use cases, all listed under "See Also".
Very versatile: same methods for providing variables, array components, function results, etc.
I made a function to do what you want. i made it "quck-and-dirty" because i have not much time to refactorize it, maybe i upload it to my github.
EDIT: a bug correction...
Use it like
formattemplatter(
'$who likes $what'
, array(
'who' => 'Tim'
, 'what' => 'Kung Pao'
)
);
Variables can be [a-zA-Z0-9_] only.
function formattemplater($string, $params) {
// Determine largest string
$largest = 0;
foreach(array_keys($params) as $k) {
if(($l=strlen($k)) > $largest) $largest=$l;
}
$buff = '';
$cp = false; // Conditional parenthesis
$ip = false; // Inside parameter
$isp = false; // Is set parameter
$bl = 1; // buffer length
$param = ''; // current parameter
$out = ''; // output string
$string .= '!';
for($sc=0,$c=$oc='';isset($string{$sc});++$sc,++$bl) {
$c = $string{$sc};
if($ip) {
$a = ord($c);
if(!($a == 95 || ( // underscore
($a >= 48 && $a <= 57) // 0-9
|| ($a >= 65 && $a <= 90) // A-Z
|| ($a >= 97 && $a <= 122) // a-z
)
)) {
$isp = isset($params[$buff]);
if(!$cp && !$isp) {
trigger_error(
sprintf(
__FUNCTION__.': the parameter "%s" is not defined'
, $buff
)
, E_USER_ERROR
);
} elseif(!$cp || $isp) {
$out .= $params[$buff];
}
$isp = $isp && !empty($params[$buff]);
$oc = $buff = '';
$bl = 0;
$ip = false;
}
}
if($cp && $c === ')') {
$out .= $buff;
$cp = $isp = false;
$c = $buff = '';
$bl = 0;
}
if(($cp && $isp) || $ip)
$buff .= $c;
if($c === '$' && $oc !== '\\') {
if($oc === '(') $cp = true;
else $out .= $oc;
$ip = true;
$buff = $c = $oc = '';
$bl = 0;
}
if(!$cp && $bl > $largest) {
$buff = substr($buff, - $largest);
$bl = $largest;
}
if(!$ip && ( !$cp || ($cp && $isp))) {
$out .= $oc;
if(!$cp) $oc = $c;
}
}
return $out;
}
Just for the sake of completeness: there is also Heredoc.
$template = fn( $who, $what ) => <<<EOT
$who likes $what
EOT;
echo( $template( 'tim', 'kung pao' ) );
Outputs:
tim likes kung pao
Sidenotes:
You get highlighting in your favourite language (if properly configured). Just substitute EOT (from the sample above) with whatever you like (e.c. HTML, SQL, PHP, ...).
Escape arrays with curly braces {$data['who']}. Accessing objekts like $data->who works without braces.
Arrow functions like fn($a)=>$a are available since PHP 7.4. You can write function($a){return $a;} if you are using PHP<7.4.
I am trying to build a multidimensional array in PHP using an array of string that I am cutting up so the string of 1:wlrb#yahoo.com:7:8.35 becomes
"id": "1",
"email_address": "wlrb#yahoo.com",
"domain": "yahoo.com",
"number_of_orders": "7",
"total_order_value": "£8.35"
In JavaScript I would create an object containing the above values and put it into an array but what is the equivalent in PHP?
So far I have the below code which gives me
Array ( [0] => stdClass Object ( [id] => 1 [email] => wlrb#yahoo.com
[domain] => yahoo.com [number_of_orders] => 7 [total_order_value] =>
8.35 )
<?php
$data = file_get_contents('orderdata');
/* echo $data; */
$lines = explode("\n", $data);
array_splice($lines, 0, 8);
/* var_dump($lines); */
array_splice($lines, -3);
/* var_dump($lines); */
/* removes all NULL, FALSE and Empty Strings but leaves 0 (zero) values */
$lines = array_values(array_filter($lines, 'strlen'));
function arraySort($lines ,$i) {
$rep = new stdClass();
$rep->id = strId($lines, $i);
$rep->email = strEmail($lines, $i);
$rep->domain = strDomain($lines, $i);
$rep->number_of_orders = orderNo($lines, $i);
$rep->total_order_value = orderValue($lines, $i);
/* var_dump($rep); */
return $rep;
}
function strDomain($lines, $i) {
if($lines[$i] == null){
return "";
}
else {
$str = $lines[$i];
$splt = explode(':', $str);
$domain = explode('#', $splt[1]);
return $domain[1];
}
}
function strId($lines, $i) {
if($lines[$i] == null){
return "";
}
else {
$str = $lines[$i];
$splt = explode(':', $str);
return $splt[0];
}
}
function strEmail($lines, $i) {
if($lines[$i] == null){
return "";
}
else {
$str = $lines[$i];
$splt = explode(':', $str);
return $splt[1];
}
}
function orderNo($lines, $i) {
if($lines[$i] == null){
return "";
}
else {
$str = $lines[$i];
$splt = explode(':', $str);
return $splt[2];
}
}
function orderValue($lines, $i) {
if($lines[$i] == null){
return "";
}
else {
$str = $lines[$i];
$splt = explode(':', $str);
return '£' + $splt[3];
}
}
$reports = array();
$reps = array();
for($i = 0, $length = count($lines); $i < $length; ++$i) {
$reps = arraySort($lines, $i);
array_push($reports, $reps);
}
?>
but when I try to search the array with
$filteredArray =
array_filter($reports, function($element) use($search){
return isset($element['domain']) && $element['domain'] == $search;
});
I get the following error
Fatal error: Uncaught Error: Cannot use object of type stdClass as
array in phpData.php:110 Stack trace: #0
[internal function]: {closure}(Object(stdClass)) #1
phpData.php(111): array_filter(Array,
Object(Closure)) #2 {main} thrown in
phpData.php on line 110
Is this because of the use of $rep = new stdClass();in my arraySort function? If so what should I be using?
The easiest and shortest solution would be :
$value = "1:wlrb#yahoo.com:7:8.35";
$keys = array('id', 'email_address', 'number_of_orders', 'total_order_value');
$fused = array_combine($keys, explode(':', $value));
$fused['domain'] = strDomain($fused['email_address']); //using your "strDomain()" function
It will give you the array you want, except you won't have the £ sign in your order value.
you can use object obtention method like this:
$filteredArray =
array_filter($reports, function($element) use($search){
return isset($element->domain) && $element->domain == $search;
});
I want to get the position of a sub string from a string using php. I can use strpos(), but it returns only first occurrence. How can I get the positions of multiple occurrence.
From: http://www.php.net/manual/en/function.strpos.php#108426
function strpos_r($haystack, $needle)
{
if(strlen($needle) > strlen($haystack))
trigger_error(sprintf("%s: length of argument 2 must be <= argument 1", __FUNCTION__), E_USER_WARNING);
$seeks = array();
while($seek = strrpos($haystack, $needle))
{
array_push($seeks, $seek);
$haystack = substr($haystack, 0, $seek);
}
return $seeks;
}
This will return an array with the position of occurences.
From the manual, there are such functions in the comments.
function strpos_recursive($haystack, $needle, $offset = 0, &$results = array()) {
$offset = strpos($haystack, $needle, $offset);
if($offset === false) {
return $results;
} else {
$results[] = $offset;
return strpos_recursive($haystack, $needle, ($offset + 1), $results);
}
}
Third parameter of strpos, has a $offset you can use:
$positions_of_string = array();
$str_to_find = "string to find";
$str_length = strlen( $str_to_find );
$last_found = 0 - $str_length;
while( false !== $last_found ) {
$last_found = strpos( $the_string, $str_to_find, $last_found+$str_length );
if( false !== $last_found )
$positions_of_strings[] = $last_found;
}
I have an uri and want to get an array of parts starting with full uri descent until I have only the first uri segment as part. I also have domain but the segments get stitched starting from the end back.
String 'a/b/c' to array('a/b/c', 'a/b', 'a')
String 'test.domain.com' to array('test.domain.com', 'domain.com', 'com')
Based on my initial method and the second method from the answers and a third method I like to know which one is the quickest and maybe there is a fourth method even better...
Method 1:
function explode_special($delimiter, $string, $reverse = false)
{
if (strpos($string, $delimiter) === false) return array($string);
$segments = explode($delimiter, $string);
if ( ! $reverse)
{
array_walk($segments, function($v, $k, $d) use(&$segments, &$delimiter) {
$d[$k] = implode($delimiter, array_slice($segments, 0, (count($segments) - $k)));
}, &$parts);
}
else
{
array_walk($segments, function($v, $k, $d) use(&$segments, &$delimiter) {
$d[$k] = implode($delimiter, array_slice($segments, $k));
}, &$parts);
}
return $parts;
}
Method 2:
function explode_special($delimiter, $string, $reverse = false)
{
if (strpos($string, $delimiter) === false) return array($string);
$segments = explode($delimiter, $string);
$parts = array();
while ($segments)
{
$parts[] = implode($delimiter, $segments);
if ($reverse)
{
array_shift($segments);
}
else
{
array_pop($segments);
}
}
return $parts;
}
Method 3:
function explode_special($delimiter, $string, $reverse = false)
{
if (strpos($string, $delimiter) === false) return array($string);
$parts = array($string);
for ($i = substr_count($string, $delimiter); $i > 0; $i--)
{
$parts[] = $string = $reverse
// ? trim(strstr($string, $delimiter), $delimiter)
? substr($string, strpos($string, $delimiter) + 1)
: substr($string, 0, strrpos($string, $delimiter));
}
return $parts;
}
Output:
// explode_special('/', 'a/b/c') = array('a/b/c', 'a/b', 'c');
// explode_special('.', 'test.domain.com', true) =
// array('test.domain.com', 'domain.com', 'com');
function multisplit($delim,$str) {
$parts = explode($delim,$str);
$arr = Array();
while($parts) {
$arr[] = implode($delim,$parts);
array_pop($parts);
}
return $arr;
}
With that function, you can just call multisplit("/","a/b/c"); and it will return ['a/b/c','a/b','a']. Similarly, call multisplit(".","test.domain.com"); and get ['test.domain.com','test.domain','test'].
I came up with the following
<?php
header("Content-type: text/plain"); //For display purposes only
/**
* Returns an array of strings with diminishing elements of $string according to $delimiter
*
* #param string $string The string
* #param string $delimiter The delimiter
*
* #return string[] Array of strings
*/
function degrading_split($string, $delimiter) {
$exploded = explode($delimiter, $string); //Array version of the string
$result = array(); //Initialize result array
foreach ($exploded as $key => $value) { //Iterate the array
$result[] = implode( #Implode...
$delimiter, #Using the original delimiter
array_slice($exploded, $key) #The complete $exploded array starting from the current $key
);
}
return $result;
}
print_r(degrading_split("a/b/c/d", "/"));
What you think about this with regex
function splitString($string,$delim) {
$arr = array();
preg_match_all('~['.$delim.']~U',$string,$delimCounter);
$arr[] = $string;
for ($i = 0;$i<count($delimCounter[0]);$i++) {
$string = $arr[] = preg_replace('~^[^.]*['.$delim.']~',"",$string);
}
return $arr;
}
Python has a feature called template strings.
>>> from string import Template
>>> s = Template('$who likes $what')
>>> s.substitute(who='tim', what='kung pao')
'tim likes kung pao'
I know that PHP allows you to write:
"Hello $person"
and have $person substituted, but the templates can be reused in various sections of the code?
You can use template strings like this:
$name = "Maria";
$info["last_name"] = "Warner";
echo "Hello {$name} {$info["last_name"]}";
This will echo Hello Maria Warner.
You could also use strtr:
$template = '$who likes $what';
$vars = array(
'$who' => 'tim',
'$what' => 'kung pao',
);
echo strtr($template, $vars);
Outputs:
tim likes kung pao
I think there are a bunch of ways to do this... but this comes to mind.
$search = array('%who%', '%what_id%');
$replace = array('tim', 'kung pao');
$conference_target = str_replace(
$search,
$replace,
"%who% likes %what%"
);
Ha, we even had one in our framework using vsprintf:
class Helper_StringFormat {
public static function sprintf($format, array $args = array()) {
$arg_nums = array_slice(array_flip(array_keys(array(0 => 0) + $args)), 1);
for ($pos = 0; preg_match('/(?<=%)\(([a-zA-Z_]\w*)\)/', $format, $match, PREG_OFFSET_CAPTURE, $pos);) {
$arg_pos = $match[0][2];
$arg_len = strlen($match[0][0]);
$arg_key = $match[1][0];
if (! array_key_exists($arg_key, $arg_nums)) {
user_error("sprintfn(): Missing argument '${arg_key}'", E_USER_WARNING);
return false;
}
$format = substr_replace($format, $replace = $arg_nums[$arg_key] . '$', $arg_pos, $arg_len);
$pos = $arg_pos + strlen($replace);
}
return vsprintf($format, array_values($args));
}
}
Which looks like it came from the sprintf page
This allows for calls like:
sprintfn('second: %(second)s ; first: %(first)s', array(
'first' => '1st',
'second'=> '2nd'
));
UPDATE
Here is an update to do what you want... not fully tested though
class Helper_StringFormat {
public static function sprintf($format, array $args = array()) {
$arg_nums = array_slice(array_flip(array_keys(array(0 => 0) + $args)), 1);
for ($pos = 0; preg_match('/(?<=%)\(([a-zA-Z_][\w\s]*)\)/', $format, $match, PREG_OFFSET_CAPTURE, $pos);) {
$arg_pos = $match[0][1];
$arg_len = strlen($match[0][0]);
$arg_key = $match[1][0];
if (! array_key_exists($arg_key, $arg_nums)) {
user_error("sprintfn(): Missing argument '${arg_key}'", E_USER_WARNING);
return false;
}
$format = substr_replace($format, $replace = $arg_nums[$arg_key] . '$', $arg_pos, $arg_len);
$pos = $arg_pos + strlen($replace); // skip to end of replacement for next iteration
}
return vsprintf($format, array_values($args));
}
}
$str = "%(my var)s now work with a slight %(my var2)s";
$repl = array("my var" => "Spaces", "my var2" => "modification.");
echo Helper_StringFormat::sprintf($str, $repl);
OUTPUT
Spaces now work with a slight modification.
Another more simple approach would be this:
$s = function ($vars) {
extract($vars);
return "$who likes $what";
};
echo $s(['who' => 'Tim', 'what' => 'King Pao']); // Tim likes King Pao
And yes, PHPStorm will complain...
I personally most like sprintf (or vsprintf, for an array of arguments). It places them in the intended order, coerces types as needed, and has a lot more advanced features available.
Example:
$var = sprintf("%s costs %.2f dollars", "Cookies", 1.1);
This will result in the value Cookies cost 1.10 dollars.
There's an entire family of printf functions for different use cases, all listed under "See Also".
Very versatile: same methods for providing variables, array components, function results, etc.
I made a function to do what you want. i made it "quck-and-dirty" because i have not much time to refactorize it, maybe i upload it to my github.
EDIT: a bug correction...
Use it like
formattemplatter(
'$who likes $what'
, array(
'who' => 'Tim'
, 'what' => 'Kung Pao'
)
);
Variables can be [a-zA-Z0-9_] only.
function formattemplater($string, $params) {
// Determine largest string
$largest = 0;
foreach(array_keys($params) as $k) {
if(($l=strlen($k)) > $largest) $largest=$l;
}
$buff = '';
$cp = false; // Conditional parenthesis
$ip = false; // Inside parameter
$isp = false; // Is set parameter
$bl = 1; // buffer length
$param = ''; // current parameter
$out = ''; // output string
$string .= '!';
for($sc=0,$c=$oc='';isset($string{$sc});++$sc,++$bl) {
$c = $string{$sc};
if($ip) {
$a = ord($c);
if(!($a == 95 || ( // underscore
($a >= 48 && $a <= 57) // 0-9
|| ($a >= 65 && $a <= 90) // A-Z
|| ($a >= 97 && $a <= 122) // a-z
)
)) {
$isp = isset($params[$buff]);
if(!$cp && !$isp) {
trigger_error(
sprintf(
__FUNCTION__.': the parameter "%s" is not defined'
, $buff
)
, E_USER_ERROR
);
} elseif(!$cp || $isp) {
$out .= $params[$buff];
}
$isp = $isp && !empty($params[$buff]);
$oc = $buff = '';
$bl = 0;
$ip = false;
}
}
if($cp && $c === ')') {
$out .= $buff;
$cp = $isp = false;
$c = $buff = '';
$bl = 0;
}
if(($cp && $isp) || $ip)
$buff .= $c;
if($c === '$' && $oc !== '\\') {
if($oc === '(') $cp = true;
else $out .= $oc;
$ip = true;
$buff = $c = $oc = '';
$bl = 0;
}
if(!$cp && $bl > $largest) {
$buff = substr($buff, - $largest);
$bl = $largest;
}
if(!$ip && ( !$cp || ($cp && $isp))) {
$out .= $oc;
if(!$cp) $oc = $c;
}
}
return $out;
}
Just for the sake of completeness: there is also Heredoc.
$template = fn( $who, $what ) => <<<EOT
$who likes $what
EOT;
echo( $template( 'tim', 'kung pao' ) );
Outputs:
tim likes kung pao
Sidenotes:
You get highlighting in your favourite language (if properly configured). Just substitute EOT (from the sample above) with whatever you like (e.c. HTML, SQL, PHP, ...).
Escape arrays with curly braces {$data['who']}. Accessing objekts like $data->who works without braces.
Arrow functions like fn($a)=>$a are available since PHP 7.4. You can write function($a){return $a;} if you are using PHP<7.4.