I think the major problem when limited to only use str_replace
is that you have little control over which strings have been replaced (as all occurrences are replaced at once) and you need to take special care when choosing a placeholder for the \@
escape sequence. There is the possibility that two inserted values combined would produce the placeholder string and therefore be turned into an @
char when the placeholder replacement is reverted.
The below is a brute force kind of solution that tries to handle that. It checks one placeholder at a time against the template string, the replacement values and the final string, making sure that the placeholder does not appear within any of these strings and that the number of placeholders originally introduced for \@
matches the number of placeholders reverted. You probably would like to set a default placeholder instead of xyz
(like zero char or something) that works best for you, to avoid unnecessary processing.
It can be called with both kinds of substitution patterns (@
and @<n>
) but currently they cannot be mixed.
It is not the prettiest code I have ever written, but given the str_replace
constraint it is nevertheless my shot at it and I hope it can be of some help to you.
function templateMap ($string, $array, $defaultPlaceholder = "xyz")
{
$newArray = array();
// Create an array of the subject string and replacement arrays
$knownStrings = array($string);
foreach ($array as $subArray) {
if (is_array($subArray)) {
$knownStrings = array_merge($knownStrings, array_values($subArray));
}
else {
$knownStrings[] = $subArray;
}
}
$placeHolder = '';
while (true) {
if (!$placeHolder) {
// This is the first try, so let's try the default placeholder
$placeHolder = $defaultPlaceholder;
}
else {
// We've been here before - we need to try another placeholder
$placeHolder = uniqid('bs-placeholder-', true);
}
// Try to find a placeholder that does not appear in any of the strings
foreach ($knownStrings as $knownString) {
// Does $placeHolder exist in $knownString?
str_replace($placeHolder, 'whatever', $knownString, $count);
if ($count > 0) {
// Placeholder candidate was found in one of the strings
continue 2; // Start over
}
}
// Will go for placeholder "$placeHolder"
foreach ($array as $subArray) {
$newString = $string;
// Apply placeholder for \@ - remember number of replacements
$newString = str_replace(
'\@', $placeHolder, $newString, $numberOfFirstReplacements
);
if (is_array($subArray)) {
// Make substitution on @<n>
for ($i = 0; $i <= 9; $i++) {
@$newString = str_replace("@$i", $subArray[$i], $newString);
}
}
else {
// Make substitution on @
@$newString = str_replace("@", $subArray, $newString);
}
// Revert placeholder for \@ - remember number of replacements
$newString = str_replace(
$placeHolder, '@', $newString, $numberOfSecondReplacements
);
if ($numberOfFirstReplacements != $numberOfSecondReplacements) {
// Darn - value substitution caused used placeholder to appear,
// ruining our day - we need some other placeholder
$newArray = array();
continue 2;
}
// Looks promising
$newArray[] = $newString;
}
// All is well that ends well
break;
}
return $newArray;
}
$a = templateMap(
"(@ and one escaped \@)",
array(" col1 < 5 && col2 > 6", " col3 < 3 || col4 > 7")
);
print_r($a);
$a = templateMap(
"You can tweet @0 \@2 @1",
array(
array("Sarah", "ssarahtweetz"),
array("John", "jjohnsthetweetiest"),
)
);
print_r($a);
Output:
Array
(
[0] => ( col1 < 5 && col2 > 6 and one escaped @)
[1] => ( col3 < 3 || col4 > 7 and one escaped @)
)
Array
(
[0] => You can tweet Sarah @2 ssarahtweetz
[1] => You can tweet John @2 jjohnsthetweetiest
)