php – Include inside class and access to $this, self or static

Question:

I found this little code inside the Composer/ClassLoader.php folder, in a project where I use composer .

 /**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}

The translated comment could be:

Isolated scope for include . Prevent access to $this and self

Tests

Taking into account the case mentioned above, I realized that in Laravel 4, I have access to $this in any view!

And see what happens in the following code in any view:

@extends('layout.default')
<?php
   $this->compiler = 1;
?>

The following error is generated

Call to a member function isExpired() on a non-object

And the interesting detail is that, as the include would be directly in the class method, I could access a protected property and assign it an unexpected value for the framework!

In other frameworks like Zend , $this can also be accessed in the view .

Questions

So, I had a few questions:

  • What are other possible problems caused by an include inside the class and having access to $this – besides the ones already mentioned?

  • In an MVC structure, in the class that represents the view , should I leave free access to $this , or use another class to "be the $this " of the view, or something else?

  • When would it be recommended to enclose the include function and when not (always, never or in most cases)?

Answer:

Answering my second question, I would vote in favor of changing the context of the included file, when it comes to the view in MVC .

And I explain:

As for the context applied to the include given within a class that represents a view , I had already developed a solution a little while ago – which can be improved by the community 🙂

Problem

  • I have a class, responsible for rendering the view . However, I want to prevent the members of this class from being accessed through $this , but at the same time I want the template that will be rendered to have its "own $this ".

Solution

  • When rendering, use a Closure to enclose the included template and, at the same time, define another object as a context for the Closure .

I worked it out as follows:

class View
{
    protected $file;

    protected $data = [];

    public function __construct($file, array $data = [])
    {
        $this->file = $file;

        $this->data = $data;
    }

    public function render()
    {
        return $this->createClosureContext();
    }

    protected function createClosureContext()
    {
        // Cria parâmetros de fachada apenas para obrigar o tipo de argumento

        $caller = function ($file, array $data) {

            unset($file, $data);

            ob_start();

            // Não passa variável, para não colidir com "extract"

            extract(func_get_arg(1));
            
            include_once func_get_arg(0); 

            return ob_get_clean();
        };

        return $caller->bindTo($this->createObjectContext())->__invoke($this->file, $this->data);
    }

    protected function createObjectContext()
    {
        $object = new StdClass;

        $object->Html = (object)'Simulate instance of Helper';

        return $object;
    }
}

Explanation

Like any [simple] template class, View takes as a parameter the name of the template and the data that will be sent to the template.

The snippet where the "magic" regarding accessing $this is here:

protected function createClosureContext()
{
    // Cria parâmetros de fachada apenas para obrigar o tipo de argumento

    $caller = function ($file, array $data) {

        unset($file, $data);

        ob_start();

        // Não passa variável, para não colidir com "extract"

        extract(func_get_arg(1));
        
        include func_get_arg(0); 

        return ob_get_clean();
    };

    return $caller->bindTo($this->createObjectContext())->__invoke($this->file, $this->data);
}

I created a closure called $caller and, through the bindTo method, defined which object will be used as $this inside this Closure . This Closure is responsible for including the file.

The object passed in bindTo is a simple stdClass , which could be any object. In this case, I defined it inside the View::createObjectContext() method.

When we instantiate, we can conclude by testing that the context of $this (for the template included by the view) has been properly changed.

Example:

new View('tpl.php', ['nome' => 'Wallace']);

No template tpl.php:

<?php echo $nome ?>
<?php print_r($this) ?>

Print:

Wallace    
stdClass Object
(
    [Html] => stdClass Object
        (
            [scalar] => Simulate instance of Helper
        )

)
Scroll to Top