You're creating sort of a template system. You can either re-invent the wheel (sort of) by coding this on your own or just using a lighweight template system like mustache.
For a very lightweight approach you can make use of regular expressions to formulate the syntax of your template variables. Just define how a variable can be written, then extract the names/labels used and replace it at will.
A function to use for this is preg_replace_callback. Here is some little example code (Demo) which only reflects simple substitution, however, you can modify the replace routine to access the values you need (in this example, I'm using a variable that is either an Array or implements ArrayAccess):
<?php
$template = <<<EOD
This is my template,
I can use [vars] at free [will].
EOD;
class Template
{
private $template;
private $vars;
public function __construct($template, $vars)
{
$this->template = $template;
$this->vars = $vars;
}
public function replace(array $matches)
{
list(, $var) = $matches;
if (isset($this->vars[$var]))
{
return $this->vars[$var];
}
return sprintf('<<undefined:%s>>', $var);
}
public function substituteVars()
{
$pattern = '~\[([a-z_]{3,})\]~';
$callback = array($this, 'replace');
return preg_replace_callback($pattern, $callback, $this->template );
}
}
$templ = new Template($template, array('vars' => 'variables'));
echo $templ->substituteVars();
This does not look spectacular so far, it's just substituting the template tags to a value. However, as already mentioned you can now inject a resolver into the template that can resolve template tags to a value instead of using an simple array.
You've outlined in your question that you would like to use the _ symbol to separate from object members / functions. The following is a resolver class that will resolve all global variables to that notation. It shows how to handle both, object members and methods and how to traverse variables. However, it does not resolve to $this but to the global namespace:
/**
* Resolve template variables from the global namespace
*/
class GlobalResolver implements ArrayAccess
{
private function resolve($offset)
{
$stack = explode('_', $offset);
return $this->resolveOn($stack, $GLOBALS);
}
private function resolveOn($stack, $base)
{
$c = count($stack);
if (!$c)
return array(false, NULL);
$var = array_shift($stack);
$varIsset = isset($base[$var]);
# non-set variables don't count
if (!$varIsset)
{
return array($varIsset, NULL);
}
# simple variable
if (1 === $c)
{
return array($varIsset, $base[$var]);
}
# descendant
$operator = $stack[0];
$subject = $base[$var];
$desc = $this->resolvePair($subject, $operator);
if (2 === $c || !$desc[0])
return $desc;
$base = array($operator => $desc[1]);
return $this->resolveOn($stack, $base);
}
private function resolvePair($subject, $operator)
{
if (is_object($subject))
{
if (property_exists($subject, $operator))
{
return array(true, $subject->$operator);
}
if (method_exists($subject, $operator))
{
return array(true, $subject->$operator());
}
}
if (is_array($subject))
{
if (array_key_exists($operator, $subject))
{
return array(true, $subject[$operator]);
}
}
return array(false, NULL);
}
public function offsetExists($offset)
{
list($isset) = $this->resolve($offset);
return $isset;
}
public function offsetGet($offset)
{
list($isset, $value) = $this->resolve($offset);
return $value;
}
public function offsetSet ($offset, $value)
{
throw new BadMethodCallException('Read only.');
}
public function offsetUnset($offset)
{
throw new BadMethodCallException('Read only.');
}
}
This resolver class can be used then to make use of some example values:
/**
* fill the global namespace with some classes and variables
*/
class Foo
{
public $member = 'object member';
public function func()
{
return 'function result';
}
public function child()
{
$child->member = 'child member';
return $child;
}
}
$vars = 'variables';
$foo = new Foo;
$template = <<<EOD
This is my template,
I can use [vars] at free [foo_func] or [foo_member] and even [foo_child_member].
EOD;
/**
* this time use the template with it's own resolver class
*/
$templ = new Template($template, new GlobalResolver);
echo $templ->substituteVars();
See the full demo in action.
This will only need a slight modification to fit your needs then finally.