php - Replacing variables in a string -


i working on multilingual website in php , in languages files have strings contain multiple variables later filled in complete sentences.

currently placing {var_name} in string , manually replacing each occurence matching value when used.

so :

{x} created thread on {y}

becomes :

dany created thread on stack overflow

i have thought of sprintf find inconvenient because depends on order of variables can change language another.

and have checked how replace variable in string value in php? , use method.

but interested in knowing if there built-in (or maybe not) convenient way in php considering have variables named x , y in previous example, more $$ variable variable.

so instead of doing str_replace on string maybe call function :

$x = 'dany'; $y = 'stack overflow'; $lang['example'] = '{x} created thread on {y}';  echo parse($lang['example']); 

would print out :

dany created thread on stack overflow

thanks!

edit

the strings serve templates , can used multiple times different inputs.

so doing "{$x} ... {$y}" won't trick because lose template , string initialized starting values of $x , $y aren't yet determined.

i'm going add answer here because none of current answers cut mustard in view. i'll dive straight in , show code use this:

function parse(     /* string */ $subject,     array        $variables,     /* string */ $escapechar = '@',     /* string */ $errplaceholder = null ) {     $esc = preg_quote($escapechar);     $expr = "/         $esc$esc(?=$esc*+{)       | $esc{       | {(\w+)}     /x";      $callback = function($match) use($variables, $escapechar, $errplaceholder) {         switch ($match[0]) {             case $escapechar . $escapechar:                 return $escapechar;              case $escapechar . '{':                 return '{';              default:                 if (isset($variables[$match[1]])) {                     return $variables[$match[1]];                 }                  return isset($errplaceholder) ? $errplaceholder : $match[0];         }     };      return preg_replace_callback($expr, $callback, $subject); } 

what do?

in nutshell:

  • create regular expression using specified escape character match 1 of 3 sequences (more on below)
  • feed preg_replace_callback(), callback handles 2 of sequences , treats else replacement operation.
  • return resulting string

the regex

the regex matches 1 of these 3 sequences:

  • two occurrences of escape character, followed 0 or more occurrences of escape character, followed opening curly brace. first 2 occurrences of escape character consumed. replaced single occurrence of escape character.
  • a single occurrence of escape character followed opening curly brace. replaced literal open curly brace.
  • an opening curly brace, followed 1 or more perl word characters (alpha-numerics , underscore character) followed closing curly brace. treated placeholder , lookup performed name between braces in $variables array, if found return replacement value, if not return value of $errplaceholder - default null, treated special case , original placeholder returned (i.e. string not modified).

why better?

to understand why it's better, let's @ replacement approaches take other answers. one exception (the failing of compatibility php<5.4 , non-obvious behaviour), these fall 2 categories:

  • strtr() - provides no mechanism handling escape character. if input string needs literal {x} in it? strtr() not account this, , substituted value $x.
  • str_replace() - suffers same issue strtr(), , problem well. when call str_replace() array argument search/replace arguments, behaves if had called multiple times - 1 each of array of replacement pairs. means if 1 of replacement strings contains value appears later in search array, end substituting well.

to demonstrate issue str_replace(), consider following code:

$pairs = array('a' => 'b', 'b' => 'c'); echo str_replace(array_keys($pairs), array_values($pairs), 'ab'); 

now, you'd expect output here bc cc (demo) - because first iteration replaced a b, , in second iteration subject string bb - both of these occurrences of b replaced c.

this issue betrays performance consideration might not obvious - because each pair handled separately, operation o(n), each replacement pair entire string searched , single replacement operation handled. if had large subject string , lot of replacement pairs, that's sizeable operation going on under bonnet.

arguably performance consideration non-issue - need very large string , lot of replacement pairs before got meaningful slowdown, it's still worth remembering. it's worth remembering regex has performance penalties of own, in general consideration shouldn't included in decision-making process.

instead use preg_replace_callback(). visits given part of string looking matches once, within bounds of supplied regular expression. add qualifier because if write expression causes catastrophic backtracking considerably more once, in case shouldn't problem (to avoid made repetition in expression possessive).

we use preg_replace_callback() instead of preg_replace() allow apply custom logic while looking replacement string.

what allows do

the original example question

$x = 'dany'; $y = 'stack overflow'; $lang['example'] = '{x} created thread on {y}';  echo parse($lang['example']); 

this becomes:

$pairs = array(     'x' = 'dany',     'y' = 'stack overflow', );  $lang['example'] = '{x} created thread on {y}';  echo parse($lang['example'], $pairs); // dany created thread on stack overflow 

something more advanced

now let's have:

$lang['example'] = '{x} created thread on {y} , contained {x}'; // dany created thread on stack overflow , contained dany 

...and want second {x} appear literally in resulting string. using default escape character of @, change to:

$lang['example'] = '{x} created thread on {y} , contained @{x}'; // dany created thread on stack overflow , contained {x} 

ok, looks far. if @ supposed literal?

$lang['example'] = '{x} created thread on {y} , contained @@{x}'; // dany created thread on stack overflow , contained @dany 

note regular expression has been designed pay attention escape sequences immediately precede opening curly brace. means don't need escape escape character unless appears in front of placeholder.

a note use of array argument

your original code sample uses variables named same way placeholders in string. mine uses array named keys. there 2 reasons this:

  1. clarity , security - it's easier see end being substituted, , don't risk accidentally substituting variables don't want exposed. wouldn't if feed in {dbpass} , see database password, it?
  2. scope - it's not possible import variables calling scope unless caller global scope. makes function useless if called function, , importing data scope bad practice.

if really want use named variables current scope (and not recommend due aforementioned security issues) can pass result of call get_defined_vars() second argument.

a note choosing escape character

you'll notice chose @ default escape character. can use character (or sequence of characters, can more one) passing third argument - , may tempted use \ since that's many languages use, but hold on before that.

the reason don't want use \ because many languages use own escape character, means when want specify escape character in, say, php string literal, run problem:

$lang['example'] = '\\{x}';   // results in {x} $lang['example'] = '\\\{x}';  // results in \dany $lang['example'] = '\\\\{x}'; // results in \dany 

it can lead readability nightmare, , non-obvious behaviour complex patterns. pick escape character not used other language involved (for example, if using technique generate fragments of html, don't use & escape character either).

to sum up

what doing has edge-cases. solve problem properly, need use tool capable of handling edge-cases - , when comes string manipulation, tool job regex.


Comments

Popular posts from this blog

ios - UICollectionView Self Sizing Cells with Auto Layout -

node.js - ldapjs - write after end error -

DOM Manipulation in Wordpress (and elsewhere) using php -