0

I have Googled for the past couple hours trying to figure out how to do this. I just want to be clear that my issue is not this issue or that issue, because I am not trying to check inside the script if the variables are set. I am trying to check outside it, to see if they're set / passed to the included file before they're interpreted, or at least meaningfully interpreted to the point where an error is thrown. Let me explain.

A little Background

I am creating a utility package for internal usage at the company I work for. I have chosen to render templates one of two ways: including them or outputting the rendered string.

public function render($context = array()) {
    do_action(self::TAG_CLASS_NAME.'_render_view', $this, $context);
    if ( empty( $this->html ) ) {

        ob_start();
        $this->checkContext($context);
        extract( $context );
        require_once $this->getFullPath();
        $renderedView = ob_get_contents();

        ob_end_clean();

        $this->html = $renderedView;

        return $renderedView;
    } else {
        return $this->html;
    }
}

public function includeView($context = array()) {
    do_action(self::TAG_CLASS_NAME.'_include_view', $this, $context);
    extract( $context );
    include $this->getFullPath();
}

The problem

Inside of the render method, I start some output buffering. This is so I can have the interpreter evaluate the code and output the HTML as a string (without taking the eval() hit. Inside my unit tests, I experiemented with what would happen if I left out a context that was inside the template itself. For example: If I have a context array that looks like:

$context = array(
    'message' => 'Morning'
);

And an associated template that looks like this:

<?php echo "Hello ".$name."! Good ".$message; ?>

Or this

<p>Hello <?php echo $name; ?>! Good <?php echo $message; ?></p>

Doesn't matter how it's formatted, as long as the context vars are passed to it correctly. Anyway, leaving out the $name in the context will result in a "Undefined variable: $name" E_NOTICE message. Which makes sense. How do you 'capture' that undefined variable before it creates the notice?

I have tried to use:

$rh = fopen($this->getFullPath(), 'r');
$contents = fread($rh, filesize($this->getFullPath()));
fclose($rh);

Where $contents outputs:

"<?php echo sprintf("Hello %s, Good %s.", $name, $greeting); ?>"

The next logical step (for me anyway, thus the question) is to extract the vars in that string. So I briefly started down the road of creating a regex to match on that string and capture the variables, but ended up on here, because I felt like I was duplicating work. I mean, the PHP interpreter already does this effectively, so there must be a way to utilize the built-in functionality. Maybe?

All this to say, I want to do something similar to this psuedo code:

protected function checkContext($context) {
    require $filename;
    $availVars = get_defined_vars()
    if ( $availVars !== $context ) {
        setUnDefinedVar = null
    }
}

Having said that, this may not even be the right way to do it, but what is? Do I let the interpreter fail on an undefined variable upon inclusion of the file? If I let it fail, am I exposing myself to any security vulnerabilities? Note: I am not setting any variables in templates via $_GET or $_POST.

Any answers are much appreciated. Thank you ahead of time.

Community
  • 1
  • 1
mrClean
  • 415
  • 5
  • 18

1 Answers1

0

I recommend using getters and setters within your class. There may be a better solution but this is how I do it. Since you're trying to access variables in the global scope you would add the following method to the class calling the included file:

public function __get($variable)
{
    if (array_key_exists( $variable, $GLOBALS ))
        return $GLOBALS[$variable];

    return null;
}

So now in your included file you would access variables by using the

$this->{$variableName}

In your specific case, it would look like...

<?php echo "Hello ".$this->name."! Good ".$this->message; ?>

However please note, if the variable requested is defined in the scope of the calling class then that particular member variable will be returned. Not one defined in the global scope.

A better explanation of this overloading operator may be found here PHP Magic Methods

  • The variables being passed to the PHP file are set using the `context` array, which the caller defines. Then the render method uses `extract()` internally which takes each `key`/ `value` pair and turns it into a `$variable`/ value pair. So, the problem I'm facing is comparing the variables (which, thanks to `extract()`, are in the current symbol table) to the ones that exist inside the PHP file. The variables don't/ shouldn't (necessarily) exist within the calling class, so I don't think `__get()` will work for this case, tested to be sure, and it doesn't work... – mrClean Mar 28 '17 at 19:22
  • To be fair, a ternary operator would work inside the template, but before checking *every. single.* variable for set-ness in the template, I want to see if there's another way, or if I'm going about it wrong. – mrClean Mar 28 '17 at 19:25
  • So use the context array in the getter instead of $Globles and an output buffer instead of fopen(). It will do the desired result. – Richard Tyler Miles Mar 29 '17 at 19:47