Make Laravel Livewire protected and private properties and methods accessable in Blade templates

Currently Livewire does not allow access to protected or private methods and properties right from within Blade templates. A small change in the way the Template is bound to the component makes it possible.

Updated on 2021-09-01 by Dimitri König

At the time of writing this mini article Laravel Livewire does not allow access to protected or private properties and methods right from within Blade templates. I'm not sure wether it was designed that way due to security reasons or due to not knowing how/if that's possible to begin with.

Let's take a look at some code:

class ExampleComponent extends Component
{
    public $publicProperty = 'foo';
    protected $protectedProperty = 'bar';

    public function publicMethod()
    {
    }

    protected function protectedMethod()
    {
    }

    public function render()
    {
		return view('example-template');
    }
}

And the template in use:

<div>
  <p>My example template</p>
  <p>Works: {{ $publicProperty }}</p>
  <p>Works too: {{ $this->publicProperty }}</p>
  <p>Works also: {{ $this->publicMethod() }}</p>
  <p>Won't worky anyway: {{ $protectedProperty }}</p>
  <p>Won't work at the moment: {{ $this->propectedProperty</p>
  <p>Won't work either: {{ $this->protectedMethod() }}</p>
</div>

If you want to make $protectedProperty accessable, you could pass it along as parameter to the view:

public function render()
{
	return view('example-template', ['protectedProperty' => $protectedProperty]);
}

But accessing protected methods would be a real treat, since that would make simple things possible in a Vue.js style manner, like where you loop through an array an check an item using a method for something specific like is it selected or not.

There is a workaround for this: you make all properties public and use archtechx/livewire-access to secure access to this properties and methods.

But I think we can make that work easier.

The Blade template in use for the component is processed in the RendersLivewireComponents trait:

protected function evaluatePath($__path, $__data)
{
    if (! $this->isRenderingLivewireComponent()) {
        return parent::evaluatePath($__path, $__data);
    }

    $obLevel = ob_get_level();

    ob_start();

    // We'll evaluate the contents of the view inside a try/catch block so we can
    // flush out any stray output that might get out before an error occurs or
    // an exception is thrown. This prevents any partial views from leaking.
    try {
        \Closure::bind(function () use ($__path, $__data) {
            extract($__data, EXTR_SKIP);
            include $__path;
        }, end($this->livewireComponents))();
    } catch (Exception $e) {
        $this->handleViewException($e, $obLevel);
    } catch (Throwable $e) {
        $this->handleViewException($e, $obLevel);
    }

    return ltrim(ob_get_clean());
}

In line 15 we see that \Closure::bind is used to include the template and bind it to the component (second parameter).

A look at the Closure::bind docs shows us that there is a third parameter for scope:

public static Closure::bind(Closure $closure, ?object $newThis, object|string|null $newScope = "static"): ?Closure

Again from the docs:

The class scope to which the closure is to be associated, or 'static' to keep the current one. If an object is given, the type of the object will be used instead. This determines the visibility of protected and private methods of the bound object. It is not allowed to pass (an object of) an internal class as this parameter.

Now we can make a simple modification to the way the Blade template is bound to make protected and private properties and methods accessable in the components Blade template:

$component = end($this->livewireComponents);
\Closure::bind(function () use ($__path, $__data) {
    extract($__data, EXTR_SKIP);
    include $__path;
}, $component, $component)();

Adding the same component object as third parameter to Closure::bind changes the scope of the template which makes it feel like it's within the component and access to $this->protectedProperty or $this->protectedMethod() works fine.

I've pushed a PR to the Livewire Git Repo, Bind component view to component and make non-public properties and make methods available in view only #3640 , so let's hope it will be merged soon.

// Addendum: PR got merged, so let's wait for a new release.