6

I'm looking for the php equivalent of pythons % operator.

# PYTHON Example
foo = 'variable_string'
baz = 'characters'

new_string = 'my %(foo)s of %(baz)s' % locals()

My Vague php context:

Note: This is to demonstrate why I want to do this, it does not reflect any code.

// PHP Example context
class Controller {
    ...
    // Singles quotes used in intentionally
    // This template string is meant to be overloadable.
    public $template = '<h1>{$title}</h1><h2>{$subheading}</h2>'; 
    ....
}

class View {
    ...
    public function render($template) {
        $title = 'variable string';
        $subheading = 'some other variable stuff';
        // Example of the python operator
        return magically_put_variables_in_string(
            $template, 
            magically_get_named_variables_from_scope()
        );
    }
    ...
}
  • Specifically I want the most canonical magically_put_variables_in_string implementation.
  • I cannot assume I will be able to use anything from later than php 5.3
  • I do not mind passing an explicit array('$variable_name' => $variable_name)
  • Points for doing it without defining a new function.

Final Note: I have built a work around for my specific use case, however it does not satisfy the question.

Community
  • 1
  • 1
ThorSummoner
  • 16,657
  • 15
  • 135
  • 147

11 Answers11

18

A good approach would be strtr:

strtr('<h1>{foo}</h1><h2>{bar}</h2>', array(
  '{foo}' => $foo,
  '{bar}' => $bar,
));

You can also use get_defined_vars() to get an array of variables accessible in the current scope, though I personally prefer explicit enumeration.


One of the subtle advantages of using strtr over str_replace is that strtr will not search the already-substituted portions of the string for further replacements. The manual page for str_replace states:

Because str_replace() replaces left to right, it might replace a previously inserted value when doing multiple replacements.

DCoder
  • 12,962
  • 4
  • 40
  • 62
  • neat, i have read all the instructions for this function 'cos you posted it as an answer. well done for providing it as the answer. – Ryan Vincent Apr 22 '14 at 00:11
3

It sounds like you are trying to do typecast swapping. You can do this with two functions print_f and sprint_f. The first will echo the output, the second will return a string. You can pass named variables into the functions like so:

// PHP Example context
class Controller {
    ...
    // Singles quotes used in intentionally
    // This template string is meant to be overloadable.
    public $template = '<h1>%s</h1><h2>%s</h2>'; // Update this line
    ....
}

class View {
    ...
    public function render($template) {
        $title = 'variable string';
        $subheading = 'some other variable stuff';

        return sprint_f($template, $title, $subheading);
    }
    ...
}

UPDATE:

You can also target variables in typecast swapping by adding numerical specifiers to the types. Say you want the title twice for some reason:

public $template = '<h1>%1$s</h1><h2>%2$s</h2><h3>%1$s</h3>';

That will typecast swap the first variable (second argument) in the sprint_f() function in both the <h1> and the <h3>. The number you put in the specifier should match the argument location, so the %1$s will be the first argument following the $template and so on. You can have any number of typecast specifiers of any type. They are as follows:

  • % - a literal percent character. No argument is required.
  • b - the argument is treated as an integer, and presented as a binary number.
  • c - the argument is treated as an integer, and presented as the character with that ASCII value.
  • d - the argument is treated as an integer, and presented as a (signed) decimal number.
  • e - the argument is treated as scientific notation (e.g. 1.2e+2). The precision specifier stands for the number of digits after the decimal point since PHP 5.2.1. In earlier versions, it was taken as number of significant digits (one less).
  • E - like %e but uses uppercase letter (e.g. 1.2E+2).
  • f - the argument is treated as a float, and presented as a floating-point number (locale aware).
  • F - the argument is treated as a float, and presented as a floating-point number (non-locale aware). Available since PHP 4.3.10 and PHP 5.0.3.
  • g - shorter of %e and %f.
  • G - shorter of %E and %f.
  • o - the argument is treated as an integer, and presented as an octal number.
  • s - the argument is treated as and presented as a string.
  • u - the argument is treated as an integer, and presented as an unsigned decimal number.
  • x - the argument is treated as an integer and presented as a hexadecimal number (with lowercase letters).
  • X - the argument is treated as an integer and presented as a hexadecimal number (with uppercase letters).
Matthew R.
  • 4,332
  • 1
  • 24
  • 39
  • I don't believe this satisfies the constraint of allowing the template to define the order of the variables, or repeat the variables any number of times. – ThorSummoner Apr 22 '14 at 16:59
  • Check the update I posted. That should fix your issue. – Matthew R. Apr 22 '14 at 17:01
  • Also, this should benchmark faster than string functions or regex. Specifically if you are using static data types instead of interpreting. – Matthew R. Apr 22 '14 at 17:11
1

I hope str_replace is what you are looking for.

Description ¶

mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] )
This function returns a string or an array with all occurrences of search in subject replaced with the given replace value.

You can give tokens array as subject and corresponding token values as array in replace.

<?php

# PHP Example
$foo = 'variable_string'
$baz = 'characters'

$new_string = str_replace(array("%foo%","%baz%"),array($foo,$baz), "my %foo% of %baz%s");

?>


<?php
// Provides: <body text='black'>
$bodytag = str_replace("%body%", "black", "<body text='%body%'>");
?>
Akhil Thayyil
  • 9,263
  • 6
  • 34
  • 48
0

You can use sprintf.

$template = '<h1>%s</h1>'; 
echo sprintf($template, 'Hello world');
Hamatti
  • 1,210
  • 10
  • 11
  • Sorry, half the point was to have named variable substitutions. Apologies if that wasn't clean in the question. – ThorSummoner Apr 16 '14 at 00:47
  • Then I think there is no straight way to do it in vanilla PHP, don't know what some frameworks might provide. But writing your own function shouldn't be too hard. – Hamatti Apr 16 '14 at 01:06
0

Best way I can think of:

public function render($template) {
     $data = array(
                 '{$title}' => 'variable string', 
                 '{$subheading}' => 'other variable string'
                 );

     return str_replace(array_keys($data), array_values($data), $template);
}  
grimmdude
  • 374
  • 4
  • 7
0

What I always loved about PHP was that it is preserving the underlying logic of strings as arrays of characters. In php you canb do just as you would in C ...

The code hereunder will replace all escaped names with whatever is defined in the replacements array (or with nothing if nothing is defined).

$replacements['color1'] = 'red';
$replacements['color2'] = 'green';

$template = "Apples are {color1} , grapes are {color2}\n";

// this will oputput 'Apples are red , grapes are green' and a newline
echo templatereplace($template,$replacements);


function templatereplace($template,$replacements)
{
    // our output
    $outputstring = "";

    // run through the template character by character
    for($n=0 ; $n<strlen($template) ; $n++)
    {

            // if the escaped string starts
            if($template[$n] == '{')
            {
                    // reset the name of the replacement variable
                    $replacementvar = '';

                    // set the flag
                    $escaped = 1;  

                    // next round
                    continue;
            }

            // if the escaped string part stops
            if($template[$n] == '}')
            {
                    //echo "end\n";

                    // write the replacementvariable to outputstring
                    $outputstring .= $replacements[$replacementvar];

                    // set the flag
                    $escaped = 0; 

                    // next round
                    continue;

            }

            // if we are in escapes state
            if($escaped == 1)
                    // write current char to the replacementvar
                    $replacementvar .= $template[$n];
            else    
                    // write the current char to output string
                    $outputstring .= $template[$n];

            }

        // return the output string
        return $outputstring;
}

Note: Differently from the str_replace and strtr solutions here above, this will also replace escaped variables that are present in the template but not defined in the replacement scope.

Freud Chicken
  • 525
  • 3
  • 8
  • This brings up one of my bigest frustrations with php, How to access a character/substring range in php via string indexing, as far as I can tell It simply isn't possible to do. Ex: `"hello world"[3,-2] === "lo wor"` – ThorSummoner Apr 21 '14 at 20:50
  • 1
    with the substr($haystack,$start,$length) function: – Freud Chicken Apr 21 '14 at 22:09
  • :| No, that's a function call, not language syntax. #whyihatephp – ThorSummoner Apr 22 '14 at 16:53
  • Indeed but whats 'language syntax' really. In the mentioned case we are looking at assigning or reading 8bit data units from memory addresses. var $txt = "bla", is really char *txt; txt = 'b'; (txt+1) = 'l'; (txt+2) = 'a'. And this would then be some evene n more arcane stuff in asm and so on. The point being that a good 5th gen language should preserve the logic of the underlying layers. That why I generally prefer php over e.g. perl – Freud Chicken Apr 23 '14 at 11:29
0

So basically, you want to do the following, but delaying the "parsing" of the string until you can define your variables in your own function?

$foo = 'variable_string';
$baz = 'characters';

$new_string = "my {$foo}s of {$baz}s";
echo $new_string; // 'my variable_strings of characterss'

As DCoder suggested in one of his answers, you can use the get_defined_vars(); function and go from there. Here's a solution using that method.

<?php

$template = '<h1>{$title}</h1><h2>{$subheading}</h2>'; 

function render($str) {
    $title = 'Delayed String Parsing';
    $subheading = 'As demonstrated by the following sample code';

    $defined_variables = get_defined_vars();

    $defined_variable_keys = array_keys($defined_variables);
    $parsed_string = $str;
    for ($i = 0; $i < count($defined_variable_keys); $i++) {
        $var_name = $defined_variable_keys[$i];
        $parsed_string = str_replace('{$' . $var_name . '}', $defined_variables[$var_name], $parsed_string);
    }
    return $parsed_string;
}

echo render($template);

You can see it running here: http://ideone.com/1WMQSr

dAngelov
  • 830
  • 6
  • 10
0

simply

function map($string, $variables) {
    $mapper = array();
    foreach ($variables as $name => $value) 
        $mapper["#{$name}#"] = $value;

    return strtr($string, $mapper);
}

function render($template) {
    $foo = 'variable_string';
    $baz = 'characters';

    return map($template, get_defined_vars());  
}

echo render('#foo# 123 #baz#aaa');
Hieu Vo
  • 3,105
  • 30
  • 31
0

Maybe not a direct answer to your question, but it looks like you are trying to build your own MVC engine. If thats the case I find it strange that you define the template (view) in the controller and then define the logic (controller) in the view. I think it should be the other way around.

If you use it like that you can define all variables in the controller and simply use an include file as "view" and that would simplify things a lot and give you a lot more control.

<?php
class Model
{
    private $_title;
    private $_subHeading;

    final public function __construct($title, $subHeading)
    {
        $this->_title = $title;
        $this->_subheading = $subHeading;
    }

    final public function Title() { return $this->_title;   }
    final public function SubHeading() {    return $this->_subHeading;  }
}

class Controller
{
    final public function __construct()
    {
        $model = new Model("variable string","some other variable stuff");

        //in this example return isn't realy needed, but you could alter the view class to buffer the output and return it to the controller instead of just sending the output to the user directly.
        return View::Render("test.php", $model);
    }
}

class View
{
    final static public function Render($viewTemplate, $model)
    {
        //Omitted: do some checking here for file exits etc.

        //I include a masterpage first. This contains the main layout of the site and CSS/scripts etc.
        //You could expand on this to have multiple different masterpages, but for this example I kept it simple with just one.


        //you could use some output buffering around the include to be able to return the content to the controller.

        include("masterpage.php");
    }
}
?>

masterpage.php

<html>
<head>
    <title><?php echo $model->Title(); ?></title>
</head>
<body>
<?php include($viewTemplate); ?>
</body>
</html>

test.php

<h1><?php echo $model->Title(); ?></h1><h2><?php echo $model->SubHeading(); ?></h2>

How you invoke the controller is up to you. Personally I would use .htaccess to send all request to a single php file and then start the right controller based on the URL. For simple testing you could just use $c = new Controller();

Final output to user:

<html>
<head>
    <title>variable string</title>
</head>
<body>
<h1>variable string</h1><h2>some other variable stuff</h2>
</body>
</html>
Hugo Delsing
  • 13,803
  • 5
  • 45
  • 72
-1

If you trust the template enough you might give eval() a chance:

class View
{

    public function render(array $model, $template)
    {
         extract($model);
         return eval(sprintf('return "%s";', addslashes($template)));
    }

}

$template = 'Hello "${object}"';
$model = array("object" => "world");

$view = new View();
$rendered = $view->render($model, $template);

echo $rendered, "\n";
Markus Malkusch
  • 7,738
  • 2
  • 38
  • 67
  • I indeed was contemplating an eval-based solution, though I wasn't sure how to do it best myself, Colleagues often "talk shit" about eval in practice, though simultaneous praise includes of generated files. Thank you for satisfying a sub-curiosity of mine! – ThorSummoner Apr 20 '14 at 08:30
-1

A Code Golf style answer would be to just force re-parsing of the string with an eval:

$template = '<h1>{$title}</h1><h2>{$subheading}</h2>';

$title = 'variable string';
$subheading = 'some other variable stuff';

echo eval("return \"$template\";");

Don't do this in production, of course. :-)

Community
  • 1
  • 1
madebydavid
  • 6,457
  • 2
  • 21
  • 29