7

The only way I've found to interpolate a string (that is, expand the variables inside it) is the following:

$str = 'This is a $a';
$a = 'test';
echo eval('return "' . $str . '";');

Keep in mind that in a real-life scenario, the strings are created in different places, so I can't just replace 's with "s.

Is there a better way for expanding a single-quoted string without the use of eval()? I'm looking for something that PHP itself provides.

Please note: Using strtr() is just like using something like sprintf(). My question is different than the question linked to in the possible duplicate section of this question, since I am letting the string control how (that is, through what function calls or property accessors) it wants to obtain the content.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Parham Doustdar
  • 2,019
  • 3
  • 22
  • 41
  • 1
    Do you mean evaluate instead of interpolate? – codea Jul 23 '14 at 13:42
  • See also: [Replacing variables in a string](http://stackoverflow.com/a/18213633/1612290) – Bad Wolf Jul 23 '14 at 13:44
  • @Bad Wolf: These are custom solutions, or the translate functions abused to provide this functionality. My question is, does PHP itself allow such a thing to be done without the use of eval? – Parham Doustdar Jul 23 '14 at 13:51
  • 2
    @Codea: Replacing variable names inside a string with their values is called string interpolation. – Parham Doustdar Jul 23 '14 at 13:59
  • 1
    The actual answer to your question is No, there isn't another way for expanding single-quoted strings without using eval. eval also opens up your script to potentially be abused with script injection. String Interpolation is not the same as variable expanding where strings are parsed (look at: http://php.net/manual/en/language.types.string.php#language.types.string.parsing) – PeteAUK Jul 23 '14 at 14:18
  • Well, I'm only using these strings for creating meaningful messages for logging. I'm trying to have a central function for logging which just gets the data as an argument, and retrieves the message from a static array and expands it, then saves it. Since this is all done in code, injection is not an issue, but I'm not sure if eval is the most elegant way to do this. – Parham Doustdar Jul 23 '14 at 14:23
  • If I were doing it, I'd likely write a function that goes function logData($data, $format) and fire it the information. However as it sounds like the code is already done and would take too much effort, I'd stick with eval. In principal a global message object/function would be a lot more versatile so worth looking at for future projects :) – PeteAUK Jul 23 '14 at 14:32
  • @PeteAUK No, it hasn't been done yet, and I'm loving the fact that you're proposing a more elegant solution. Can you please elaborate in an answer so I can both accept if it solves my problem, and so that you will have more space to explain? Thanks a loooot in advance :) – Parham Doustdar Jul 23 '14 at 14:37
  • Here is a question which shows my intent more clearly: http://stackoverflow.com/questions/24914449/how-can-i-construct-log-messages-in-a-dry-fashion – Parham Doustdar Jul 23 '14 at 15:19

3 Answers3

4

There are more mechanisms than PHP string literal syntax to replace placeholders in strings! A pretty common one is sprintf:

$str = 'This is a %s';
$a   = 'test';
echo sprintf($str, $a);

http://php.net/sprintf

There are a ton of other more or less specialised templating languages. Pick the one you like best.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • 1
    Well, this string is dynamic. I will ensure that I have $a. However, based on context, $a can be an object, or an array, or so on. So my string could be something like 'this is a {$a->content}.'. The reason I can't use sprintf is I don't know the variable that the text needs to use, so I can't just use it in a sprintf. If I knew it, I might as well have used a double-quoted string. – Parham Doustdar Jul 23 '14 at 13:49
  • 1
    That's sounds like madness, really. How can you prepare your environment to contain the correct variables if you have no idea what "the string" expects? And if you're able to prepare your environment that way, why can't you pass it to `sprintf`? Anyway, this still sounds like you're looking for a templating language. Maybe something like Mustache, or Twig. – deceze Jul 23 '14 at 13:51
  • Not that complex -- I'm merely trying to have the list of log messages in an array, pass the data they need in $data, and let the code inside of that string take care of constructing the log message. – Parham Doustdar Jul 23 '14 at 13:55
  • 2
    Still not sure how exactly you're going to accomplish that. If your log message requires `"{$a->content}"`, how are you dynamically going to match that up with the right variable? String literal interpolation works because it's written right there in the source code, so you can make sure that your variables are available and match them with your string interpolation. However, if variables and strings are coming from different sources, it's virtually impossible (or at least prohibitively difficult) to match them up correctly. – deceze Jul 23 '14 at 14:09
  • Well, imagine I have some events. The code raising the event knows what code is required for the log message. It calls the listener object with that data (with the variable `$data`), and in case of using `eval()`, the listener gets that message from the array, and since `$data~ is one of the function's arguments and it's already in scope, when it evals something like `'The content is {$data->getContent()}'`, the output is something like "The content is somecontent.". Does that make sense? – Parham Doustdar Jul 23 '14 at 14:14
  • I'd still recommend a templating language then, if you insist on accessing properties and methods in a "string". Yes, it *is* overkill, but what you're doing seems inherently overkill to me anyway. :) – deceze Jul 23 '14 at 14:44
  • Is there a better way to not have loooots of functions that only contain something like `$this->logger->log('User with id ' . $data->getId() . ' has signed up.');`? – Parham Doustdar Jul 23 '14 at 14:49
  • I'd put that logger call inline with the registration code anyway. There should be one function for registering users, and that function contains `$this->logger->info(sprintf('Registered user with id %d', $user->getId()))`. Not sure why you'd have the exact same log statement elsewhere, unless your code is very WET to begin with. :) – deceze Jul 23 '14 at 14:52
  • Well, I'm trying to use an event manager to not clutter my business logic with log statements. So, when registering, I just raise an event, and let the listener (which is just a class full of these logging functions) take care of it. – Parham Doustdar Jul 23 '14 at 14:54
  • And the same log statement can be triggered from several different events, or what? Otherwise I'm still not sure what problem you're trying to solve exactly. Maybe opening a new question with more details about your app structure and what problem you're trying to solve with this would help, this doesn't seem to go anywhere right now. – deceze Jul 23 '14 at 14:58
  • http://stackoverflow.com/questions/24914449/how-can-i-construct-log-messages-in-a-dry-fashion – Parham Doustdar Jul 23 '14 at 15:19
1

Have you heard of strtr()?

It serves this very purpose and is very useful to create dynamic HTML content containing information from a database, for example.

Given the following string:

$str = 'here is some text to greet user {zUserName}';

then you can parse it using strtr():

$userName = 'Mike';
$parsed = strtr($str,array('{zUserName}'=>$userName));
echo $parsed; // outputs: 'here is some text to greet user Mike'

While sprintf is faster in some regards, strtr allows you to control what goes where in a more friendly way (sprintf is not really manageable on very long strings containing, say, a hundred placeholders to be replaced).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • My string would be something like `'Here is some text that shows the value of {$user->getAccount()->getFirstName()}'`. – Parham Doustdar Jul 23 '14 at 14:07
  • 1
    why? it's way clearer to have a string like `'Here is some text that shows the value of {zWhatever}'` then replace that whatever with whatever you wish. it's also way more usable, and can do what you wish, think about it a bit. you do not need to have all this content call *in* the string – Félix Adriyel Gagnon-Grenier Jul 23 '14 at 14:09
  • Well, because the content I am trying to get is a lot more dynamic. Maybe looking at my comments on @deceze's answer would help? – Parham Doustdar Jul 23 '14 at 14:15
  • 1
    I don't think you are the first person to run on this particular problem. As a matter of fact, we are a *lot* to do this, making **very** dynamic things, and both @deceze and I are trying to make you understand that you should think about this better. This method allows for very dynamic templating and content creating, and I really do not think you have gone to the extent of those possibilities yet – Félix Adriyel Gagnon-Grenier Jul 23 '14 at 14:17
  • Well, as I said, I'm going to use these strings to create a function to get $data, and pass that to a single-quoted string, so it would construct meaningful log messages. Since I don't know what properties of $data are needed in that string, I can't use placeholders. Does that make sense? – Parham Doustdar Jul 23 '14 at 14:26
  • it does make sense, but I maintain that the placeholders **need not** know what properties are needed, it just need to know **where** they will go. then you replace this placeholder with the dynamic properties the script logic find relevant – Félix Adriyel Gagnon-Grenier Jul 23 '14 at 14:30
  • You're correct. Placeholders don't need to know where the content is coming from. However, I'm talking about the reversed scenario. Imagine my function as some kind of messenger. It doesn't know what will be logged, and it doesn't know what data will be extracted from `$data`. It just needs that it must pass the `$data` variable as-is to some string so it can use it to construct a log message. That's my scenario :) – Parham Doustdar Jul 23 '14 at 14:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/57851/discussion-between-felix-gagnon-grenier-and-parham-doustdar). – Félix Adriyel Gagnon-Grenier Jul 23 '14 at 14:35
0

Here is a possible solution. I am not sure in your particular scenario if this would work for you, but it would definitely cut out the need for so many single and double quotes.

<?php
    class a {
        function b() {
            return "World";
        }
    }
    $c = new a;
    echo eval('Hello {$c->b()}.');
?>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Eric Mayfield
  • 93
  • 1
  • 10
  • Of course here, you would replace the string contents with the code you are trying to eval(). – Eric Mayfield Jul 23 '14 at 13:51
  • Great solution, thanks. However, I'm more curious to know if this can be done without the use of eval. Is there a function I can pass my string to (E.G. `'The value of the property is {$object->getRelatedObject()->getProperty()}'`), and also pass it $object, and get something like "The value of the property is somevalue.", or do I need to write that function myself? – Parham Doustdar Jul 23 '14 at 14:05
  • I'm not 100% sure I follow here, because the interpolated string should reveal that property based on the object and corresponding classes/functions you included, i.e. when you dig down through the relations the classes associated functions should do a return value much at the example I showed above. If I follow you at all I think you are asking about the ->getProperty() bit? In which case of course you would need your own function to handle the specific specifications of whatever your software is accomplishing. Like when you create your object you might have a property that get's passed cont. – Eric Mayfield Jul 23 '14 at 16:07
  • Such as `$object = new functionName($Property);` In this example your functionName would handle that $Property variable, process and return it according to whatever function you might call with object, so if you call `$object->getProperty();` It would return that, same as any other object oriented hierarchy. – Eric Mayfield Jul 23 '14 at 16:13
  • Well, no. Your answer is perfectly correct and my question is not about the contents of the string. My question is, since I've read "Eval is evil!", is there a way to not use eval? In other words, is there another way you'd recommend for doing this, rather than using eval in general? This other question shows my intent more clearly: http://stackoverflow.com/questions/24914449/how-can-i-construct-log-messages-in-a-dry-fashion – Parham Doustdar Jul 23 '14 at 16:16
  • 1
    I think this post has exactly what you are looking for. If you read down a ways the guy who answers the question has a whole lot of info about why you should stay away from eval and what to do to execute string as code in real world applications. [http://stackoverflow.com/questions/17672183/eval-does-not-return-the-function-results] – Eric Mayfield Jul 23 '14 at 16:25