tags:

views:

147

answers:

7

Can PHP dissect its own syntax? For example, I'd like to write a function that takes in an input like $object->attribute and says to itself:

OK, he's giving me $foo->bar, which means he must think that $foo is an object that has a property called bar. Before I try accessing bar and potentially get a 'Trying to get property of non-object' error, let me check whether $foo is even an object.

The end goal is to echo a value if it is set, and fail silently if not.

I want to avoid repetition like this:

<input value="<? if(is_object($foo) && is_set($foo->bar)){ echo $foo->bar; }?> "/>

...and to avoid writing a function that does the above, but has to have the object and attribute passed in separately, like this:

<input value="<? echoAttribute($foo,'bar') ?>" />

...but to instead write something which:

  • preserves the object->attribute syntax
  • is flexible: can also handle array keys or regular variables

Like this:

<input value="<? echoIfSet($foo->bar); ?> />
<input value="<? echoIfSet($baz['buzz']); ?> />
<input value="<? echoIfSet($moo); ?> />

But this all depends on PHP being able to tell me "what kind of thing am I asking for when I say $object->attribute or $array[$key]", so that my function can handle each according to its own type.

Is this possible?

Update

I got some good answers here and did some experimenting. Wanted to summarize.

  • The answer to my original question appears to be "no," but we did find a way to accomplish was I was trying to do. Techpriester pointed out that I could pass the string '$foo->bar' to a function and have it parse that and eval() it. Not the approach I want to take, but deserves a mention.
  • Peter Bailey pointed out that something like $foo->bar gets evaluated before getting passed to a function. I should have thought of this, but for some reason didn't. Thanks, Peter!
  • Will Vousden showed that passing $foo->$bar by reference allows the function to evaluate it, rather than having it evaluated beforehand. His function shows that isset($foo->bar) will not complain if $foo is not an object, which I didn't know.

More interestingly, his example seems to imply that PHP waits to evaluate a variable until it absolutely has to.

If you're passing something by value, then of course PHP has to determine the value right then. Like this:

$foo->bar = 'hi';
somefunction($foo->bar); // same as somefunction('hi');

But if you're passing by reference, it can wait to determine the value when it actually needs to.

Like this (following the order in which things happen):

echo ifSet($foo->bar); // PHP has not yet evaluated $foo->bar...
function ifSet(&$somevar){ // ...because we're passing by reference (&$somevar)
  // Now we're inside the function, but it still hasn't evaluated $foo->bar; it 
  // just made its local variable, $somevar, point to the same thing as $foo->bar
  if(isset($somevar)){ // Right HERE is where it's evaluated - when we need it
    return $bar;
  }
}

Thanks to everyone who responded! If I've misstated something, please let me know.

A: 

Are you just looking for the eval statement?

MJB
See comment to Techpriester.
Nathan Long
I see. I didn't get that from my first reading of your question. Sorry.
MJB
+1  A: 

There is a language feature that will execute any PHP code that you pass it as a string : eval().

But you really, really (as in "I'm dead serious"), should not use it. It causes much more problems than it solves and makes your code unmaintainable.

There's usually a better and cleaner way.

"eval()" leads to the dark side of the force, err I mean, of PHP.

If you just want the "obj" and "attribute" part of the supplied string, regular expression could do that. It'll be something like "/\$(\w+)->(\w+)/". You can feed this to preg_match() and it will give you the string components.

Techpriester
I don't think `eval()` is what I want. I don't want to execute `$foo->bar`; I want to take it apart into `$foo` and `->bar` and look at each piece independently. Does that make sense?
Nathan Long
A regular expression could extract the object and attribute name from "$obj->attribute". It'll be something like "/\$(\w+)->(\w+)/".
Techpriester
Passing a string and picking it apart would work. I'd rather not take that approach, but it's worth an upvote. :)
Nathan Long
+6  A: 

You can pass it by reference:

<?php

function foo(&$bar)
{
    if (isset($bar))
    {
        echo "$bar\r\n";
    }
    else
    {
        echo "It's not set!\r\n";
    }
}

$baz = new stdClass;
$baz->test = 'test';
foo($baz->test);
foo($baz->test2);

$baz = array();
foo($baz['test3']);

?>

Note that this won't work if you try to access an object as an array or vice versa.

You shouldn't try to rely on something like this too much, though.

Will Vousden
+1. Dirty hack, of course, but answers the question precisely.
stereofrog
+1 altough, in this case I will revert to the @ symbol to suppress the warnings
Eineki
This works! I'm still trying to understand why. :)
Nathan Long
I wish phantom downvoters would explain themselves!
Will Vousden
@Nathan Long: It works because the function is being passed a *reference* to the value `$foo->bar` rather than the value itself. Consequently, `$foo->bar` isn't being evaluated at the call site but rather inside the function itself, since it's bound to the parameter `$bar`. This allows the function code to check whether it's actually set.
Will Vousden
@Will - So when the function evaluates `$foo->bar`, why doesn't it complain that `$foo` isn't an object (if it isn't)?
Nathan Long
@Nathan: Because it's checking it with `isset` first. Since `isset` is a PHP language construct, it has special privileges that normal functions don't have, so something like `isset($foo->bar)` is totally fine even if `$foo` isn't even set. Essentially, `isset` is implemented such that it has access to the current stack frame, which means it can analyse the variable itself in the way you describe in your question.
Will Vousden
@Will - OK, with your explanations and some experimenting, I think I get it. I'm going to update my question so I can comment at a bit more length.
Nathan Long
+2  A: 

The reason you'll never be able to do something like this

echoIfSet($foo->bar);

Is because $foo->bar is evaluated before its sent to the function. If that value is a string, all echoIfSet() will see is a string - it has no idea that the string came from the property of an object.

So, no, in a word. PHP can't "dissect its own syntax"

Every option you have (using _get()/_set() or Reflection) involves more code and work than what you're trying to avoid - and they still don't get you exactly what you want.

Peter Bailey
I think his intention was to have `echoIfSet("$foo->bar");` but miss typed it.
Geoff
@Geoff - nope, I was just wrong. Peter, thanks for pointing out when that statement gets evaluated - good explanation. Apparently, though, passing by reference (as Will shows) allows it to get evaluated within the function it's passed to. Thanks for the help.
Nathan Long
A: 

If you are looking for something that gives you the name of the variable you passed in, as in:

function foo($variable)
{
    echo variableName($variable);
}

$test = "Hello World";
// echoes "test"
foo($test);

Then you are not going to find it. PHP doesn't have such a feature.

Chacha102
+3  A: 

maybe in this very specific case you could use the @ symbol:

<input value="<?php echo @$foo->bar; ?> "/>
Eineki
Interesting - I didn't know about that.
Nathan Long
Initially, I thought I would do this, but was dissuaded by a friend. `$foo` is created by looking up something in a database. If `$foo` isn't an object, it could be because there's no matching record, which is fine and could be ignored. But it could be because the lookup code is broken. I don't want to ignore that! And without the warning, it would be harder for me (or another maintainer) to debug. Thanks for showing me that it's possible, though.
Nathan Long
+2  A: 

Wrapping your array's and objects in a special array. you can acces the properties using php's magic methods.

class wrap implements ArrayAccess{
  private $fields=array();
  function __get($name){
    if(isset($this->fields[$name])){
      return $this->fields[$name];
    }
  }
  function __set($name, $value){
    $this->fields[$name] = $value;
  }
  // with function isset()
  public function offsetExists($offset) {
    return isset($this->$fields[$offset]);
  }

  public function offsetGet($offset) {
    return $this->$fields[$offset];
  }

  public function offsetSet($offset, $value) {
    return $this->$fields[$offset] = $value;
  }

  public function offsetUnset($offset) {
    unset($this->$fields[$offset]);
  }
}

this wil allow you to do all things you wanted.

Mathijs Henquet