how to highlight search results - php

hello i have a search function in that i will search a db for keywords. i would like to highlight the keywords and found a second function that i would like to implement into my search function.
so i have this code for the search:
<?php
function searchText($keywords){
global $db;
$returned_results = array();
$where2 = "";
$keywords = preg_split('/[\s]+/', $keywords);
$total_keywords = count($keywords);
foreach ($keywords as $key=>$keyword){
$where2 .= "`column` LIKE '%$keyword%'";
if ($key != ($total_keywords - 1)){
$where2 .= " OR ";
}
}
$results_text = "SELECT `a`, `b`, LEFT(`c`, 150) as `c` FROM `table` WHERE $where2";
$results_num_text = ($query2 = mysqli_query($db, $results_text)) ? mysqli_num_rows($query2) : 0;
if ($results_num_text === 0){
return false;
} else {
while ($row = mysqli_fetch_assoc($query2)){
$returned_results[] = array(
'ab' => $row['ab'],
'cd' => $row['cd'],
);
}
return $returned_results;
}
}
?>
and would like to implement a second function into it:
<?php
function mark_words ($text, $words, $colors = false)
{
if (!$colors || !is_array($colors) ) {
$colors = array('#ff9999', '#ffff99', '#ff99ff', '#99ffff','#99ff99');
}
$c = 0;
foreach ($words as $w) {
$w = preg_quote(trim($w));
if($w=='') {
continue;
}
$regexp = "/($w)(?![^<]+>)/i";
$replacement = '<b style="background-color:'.$colors[$c].'">\\1</b>';
$text = preg_replace ($regexp,$replacement ,$text);
$c++;
if ($c >= count($colors)) {
$c=0;
}
}
return $text;
}
$example = <<< EOT
some text is here inside
EOT;
$search = array('some','is', 'inside');
echo mark_words($example, $search);
?>
so i have this code that doesnt work:
<?php
function searchText($keywords, $colors = false){
global $db;
if (!$colors || !is_array($colors) ) {
$colors = array('#ff9999', '#ffff99', '#ff99ff', '#99ffff','#99ff99');
}
$c = 0;
$returned_results = array();
$where2 = "";
$keywords = preg_split('/[\s]+/', $keywords);
$total_keywords = count($keywords);
foreach ($keywords as $key=>$keyword){
$regexp = "/($w)(?![^<]+>)/i";
$replacement = '<b style="background-color:'.$colors[$c].'">\\1</b>';
$text = preg_replace($regexp,$replacement ,$keywords);
$c++;
if ($c >= count($colors)) {
$c=0;
}
$where2 .= "`b` LIKE '%$keyword%'";
if ($key != ($total_keywords - 1)){
$where2 .= " OR ";
}
}
$results_text = "SELECT `a`, LEFT(`b`, 150) as `b`, `c` FROM `table` WHERE $where2";
$results_num_text = ($query2 = mysqli_query($db, $results_text)) ? mysqli_num_rows($query2) : 0;
if ($results_num_text === 0){
return false;
} else {
while ($row = mysqli_fetch_assoc($query2)){
$returned_results[] = array(
'ab' => $row['a'],
'cd' => $row['b'],
);
}
return $returned_results;
$highlight = array($keywords);
echo mark_words($highlight);
}
}
?>
as i looked for it how to do so i found two possibilities. the first would be a function the second would be directly to highlight it from the select query:
SELECT
REPLACE(`col`, 'foobar', '<span class="highlight">foobar</span>') AS `formated_foobar`
FROM
…
WHERE
`col` LIKE "%foobar%"
so my question is how can i implement the second function into the search function or would it be better to use the second method?
if there is someone who could help me i really would appreciate. thanks a lot.

You shouldn't make it too hard for yourself. All you need it to replace every occurrence of a word with the word wrapped in the span with the required style applied. This should work for you:
function highlight_word( $content, $word, $color ) {
$replace = '<span style="background-color: ' . $color . ';">' . $word . '</span>'; // create replacement
$content = str_replace( $word, $replace, $content ); // replace content
return $content; // return highlighted data
}
function highlight_words( $content, $words, $colors ) {
$color_index = 0; // index of color (assuming it's an array)
// loop through words
foreach( $words as $word ) {
$content = highlight_word( $content, $word, $colors[$color_index] ); // highlight word
$color_index = ( $color_index + 1 ) % count( $colors ); // get next color index
}
return $content; // return highlighted data
}
// words to find
$words = array(
'normal',
'text'
);
// colors to use
$colors = array(
'#88ccff',
'#cc88ff'
);
// faking your results_text
$results_text = array(
array(
'ab' => 'AB #1',
'cd' => 'Some normal text with normal words isn\'t abnormal at all'
), array(
'ab' => 'AB #2',
'cd' => 'This is another text containing very normal content'
)
);
// loop through results (assuming $output1 is true)
foreach( $results_text as $result ) {
$result['cd'] = highlight_words( $result['cd'], $words, $colors );
echo '<fieldset><p>ab: ' . $result['ab'] . '<br />cd: ' . $result['cd'] . '</p></fieldset>';
}
Using Regular Expressions to replace content would do as well, though using str_replace() is a bit faster.
The functions accepts these arguments:
highlight_word( string, string, string );
highlight_words( string, array, array );
The above example results in:

By using str_ireplace instead of str_replace, the function will work case insensitive

I would not use the SQL method. As time goes on, and you have more and more highlighting rules, that will become unmanageable. Also trickier to handle the cases where you need to highlight foo differently to foobar, but one contains the other.
Separate your data handling from your formatting.

Related

php variable interpolation inside variable [duplicate]

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.

How to splitting words match by patterns with PHP

I have an array of words. e.g.,
$pattern = ['in', 'do', 'mie', 'indo'];
I wanna split a words match by the patterns to some ways.
input =
indomie
to output =
$ in, do, mie
$ indo, mie
any suggests?
*ps sorry for bad english. thank you very much!
it was an very interesting question.
Input:-
$inputSting = "indomie";
$pattern = ['in', 'do', 'mie', 'indo','dom','ie','indomi','e'];
Output:-
in,do,mie
in,dom,ie
indo,mie
indomi,e
Approach to this challenge
Get the pattern string length
Get the all the possible combination of matrix
Check whether the pattern matching.
if i understand your question correctly then above #V. Prince answer will work only for finding max two pattern.
function sampling($chars, $size, $combinations = array()) {
if (empty($combinations)) {
$combinations = $chars;
}
if ($size == 1) {
return $combinations;
}
$new_combinations = array();
foreach ($combinations as $combination) {
foreach ($chars as $char) {
$new_combinations[] = $combination . $char;
}
}
return sampling($chars, $size - 1, $new_combinations);
}
function splitbyPattern($inputSting, $pattern)
{
$patternLength= array();
// Get the each pattern string Length
foreach ($pattern as $length) {
if (!in_array(strlen($length), $patternLength))
{
array_push($patternLength,strlen($length));
}
}
// Get all the matrix combination of pattern string length to check the probablbe match
$combination = sampling($patternLength, count($patternLength));
$MatchOutput=Array();
foreach ($combination as $comp) {
$intlen=0;
$MatchNotfound = true;
$value="";
// Loop Through the each probable combination
foreach (str_split($comp,1) as $length) {
if($intlen<=strlen($inputSting))
{
// Check whether the pattern existing
if(in_array(substr($inputSting,$intlen,$length),$pattern))
{
$value = $value.substr($inputSting,$intlen,$length).',';
}
else
{
$MatchNotfound = false;
break;
}
}
else
{
break;
}
$intlen = $intlen+$length;
}
if($MatchNotfound)
{
array_push($MatchOutput,substr($value,0,strlen($value)-1));
}
}
return array_unique($MatchOutput);
}
$inputSting = "indomie";
$pattern = ['in', 'do', 'mie', 'indo','dom','ie','indomi','e'];
$output = splitbyPattern($inputSting,$pattern);
foreach($output as $out)
{
echo $out."<br>";
}
?>
try this one..
and if this solves your concern. try to understand it.
Goodluck.
<?php
function splitString( $pattern, $string ){
$finalResult = $semiResult = $output = array();
$cnt = 0;
# first loop of patterns
foreach( $pattern as $key => $value ){
$cnt++;
if( strpos( $string, $value ) !== false ){
if( implode("",$output) != $string ){
$output[] = $value;
if( $cnt == count($pattern) ) $semiResult[] = implode( ",", $output );
}else{
$semiResult[] = implode( ",", $output );
$output = array();
$output[] = $value;
if( implode("",$output) != $string ){
$semiResult[] = implode( ",", $output );
}
}
}
}
# second loop of patterns
foreach( $semiResult as $key => $value ){
$stackString = explode(",", $value);
/* if value is not yet equal to given string loop the pattern again */
if( str_replace(",", "", $value) != $string ){
foreach( $pattern as $key => $value ){
if( !strpos(' '.implode("", $stackString), $value) ){
$stackString[] = $value;
}
}
if( implode("", $stackString) == $string ) $finalResult[] = implode(",", $stackString); # if result equal to given string
}else{
$finalResult[] = $value; # if value is already equal to given string
}
}
return $finalResult;
}
$pattern = array('in','do','mie','indo','mi','e', 'i');
$string = 'indomie';
var_dump( '<pre>',splitString( $pattern, $string ) );
?>

Multiple String Replace Based on Index

I need to replace multiple sections of a string based on their indices.
$string = '01234567890123456789';
$replacements = array(
array(3, 2, 'test'),
array(8, 2, 'haha')
);
$expected_result = '012test567haha0123456789';
Indices in $replacements are expected not to have overlaps.
I have been trying to write my own solution, split the original array into multiple pieces based on sections which needs to be replaced or not, and finally combine them:
echo str_replace_with_indices($string, $replacements);
// outputs the expected result '012test567haha0123456789'
function str_replace_with_indices ($string, $replacements) {
$string_chars = str_split($string);
$string_sections = array();
$replacing = false;
$section = 0;
foreach($string_chars as $char_idx => $char) {
if ($replacing != (($r_idx = replacing($replacements, $char_idx)) !== false)) {
$replacing = !$replacing;
$section++;
}
$string_sections[$section] = $string_sections[$section] ? $string_sections[$section] : array();
$string_sections[$section]['original'] .= $char;
if ($replacing) $string_sections[$section]['new'] = $replacements[$r_idx][2];
}
$string_result = '';
foreach($string_sections as $s) {
$string_result .= ($s['new']) ? $s['new'] : $s['original'];
}
return $string_result;
}
function replacing($replacements, $idx) {
foreach($replacements as $r_idx => $r) {
if ($idx >= $r[0] && $idx < $r[0]+$r[1]) {
return $r_idx;
}
}
return false;
}
Is there any more effective way to achieve the same result?
The above solution doesn't look elegant and feels quite long for string replacement.
Use this
$str = '01234567890123456789';
$rep = array(array(3,3,'test'), array(8,2,'haha'));
$index = 0;
$ctr = 0;
$index_strlen = 0;
foreach($rep as $s)
{
$index = $s[0]+$index_strlen;
$str = substr_replace($str, $s[2], $index, $s[1]);
$index_strlen += strlen($s[2]) - $s[1];
}
echo $str;

PHP substr but keep HTML tags?

I am wondering if there is an elegant way to trim some text but while being HTML tag aware?
For example, I have this string:
$data = '<strong>some title text here that could get very long</strong>';
And let's say I need to return/output this string on a page but would like it to be no more than X characters. Let's say 35 for this example.
Then I use:
$output = substr($data,0,20);
But now I end up with:
<strong>some title text here that
which as you can see the closing strong tags are discarded thus breaking the HTML display.
Is there a way around this? Also note that it is possible to have multiple tags in the string for example:
<p>some text here <strong>and here</strong></p>
A few mounths ago I created a special function which is solution for your problem.
Here is a function:
function substr_close_tags($code, $limit = 300)
{
if ( strlen($code) <= $limit )
{
return $code;
}
$html = substr($code, 0, $limit);
preg_match_all ( "#<([a-zA-Z]+)#", $html, $result );
foreach($result[1] AS $key => $value)
{
if ( strtolower($value) == 'br' )
{
unset($result[1][$key]);
}
}
$openedtags = $result[1];
preg_match_all ( "#</([a-zA-Z]+)>#iU", $html, $result );
$closedtags = $result[1];
foreach($closedtags AS $key => $value)
{
if ( ($k = array_search($value, $openedtags)) === FALSE )
{
continue;
}
else
{
unset($openedtags[$k]);
}
}
if ( empty($openedtags) )
{
if ( strpos($code, ' ', $limit) == $limit )
{
return $html."...";
}
else
{
return substr($code, 0, strpos($code, ' ', $limit))."...";
}
}
$position = 0;
$close_tag = '';
foreach($openedtags AS $key => $value)
{
$p = strpos($code, ('</'.$value.'>'), $limit);
if ( $p === FALSE )
{
$code .= ('</'.$value.'>');
}
else if ( $p > $position )
{
$close_tag = '</'.$value.'>';
$position = $p;
}
}
if ( $position == 0 )
{
return $code;
}
return substr($code, 0, $position).$close_tag."...";
}
Here is DEMO: http://sandbox.onlinephpfunctions.com/code/899d8137c15596a8528c871543eb005984ec0201 (click "Execute code" to check how it works).
Using #newbieuser his function, I had the same issue, like #pablo-pazos, that it was (not) breaking when $limit fell into an html tag (in my case <br /> at the r)
Fixed with some code
if ( strlen($code) <= $limit ){
return $code;
}
$html = substr($code, 0, $limit);
//We must find a . or > or space so we are sure not being in a html-tag!
//In my case there are only <br>
//If you have more tags, or html formatted text, you must do a little more and also use something like http://htmlpurifier.org/demo.php
$_find_last_char = strrpos($html, ".")+1;
if($_find_last_char > $limit/3*2){
$html_break = $_find_last_char;
}else{
$_find_last_char = strrpos($html, ">")+1;
if($_find_last_char > $limit/3*2){
$html_break = $_find_last_char;
}else{
$html_break = strrpos($html, " ");
}
}
$html = substr($html, 0, $html_break);
preg_match_all ( "#<([a-zA-Z]+)#", $html, $result );
......
substr(strip_tags($content), 0, 100)

Does PHP have a feature like Python's template strings?

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.

Categories