Skip to content
This repository has been archived by the owner on Jan 21, 2020. It is now read-only.

ZendViewRenderer: cannot set layout from within a .phtml template script #68

Closed
djozsef opened this issue Jan 21, 2016 · 10 comments
Closed
Assignees
Labels

Comments

@djozsef
Copy link

djozsef commented Jan 21, 2016

When Zend\View\Helper\Layout helper called from inside a .phtml scope throws "getRoot: no view model currently registered as root in renderer". Error is thrown at "../­vendor/­zendframework/­zend-view/­src/­Helper/­Layout.php:64" because root is not set so setTemplate() fails.

REPRO:

put this line into any non-layout template script:

$this->layout('layout/non-default.php');

Obviously the layout path/map has to be set in config/autoload/templates.global.php

RESEARCH DONE:

The root ViewModel present is null when the helper tries to set template thus it fails. I suspect, that the root cause is that the ViewModel is fetched through DIC/ServiceManager, and at this point it it is already cached with null root. It was probably created in an other run.

I have no idea how to resolve this, sorry.

Could anybody with broader conceptual overview check this?

Thanks.

@djozsef djozsef changed the title Cannot set layout from within a .phtml template script Cannot set layout from within a .phtml template script (ZendViewRenderer) Jan 21, 2016
@djozsef djozsef changed the title Cannot set layout from within a .phtml template script (ZendViewRenderer) ZendViewRenderer: cannot set layout from within a .phtml template script Jan 21, 2016
@weierophinney weierophinney self-assigned this Jan 21, 2016
@weierophinney
Copy link
Member

There are a few ways to resolve this.

First, and easiest: set a default layout in your configuration:

return [
    /* ... */
    'templates' => [
        /* ... */
        'layout' => 'layout::default', 
    ],
];

Doing so ensures that the layout view model is injected in the renderer at instantiation.

The next approach is to provide a layout at the time you call render(); the documentation details two ways to do this.

Finally, within your template, you can inject a root view model manually prior to calling layout() in your template:

use Zend\View\Model\ViewModel;

$this->viewModel()->setRoot(new ViewModel());
$this->layout(/* ... */;

// OR, in one go:
$this->viewModel()->setRoot(
    (new ViewModel())->etTemplate(/* layout you want to use */)
);

@djozsef
Copy link
Author

djozsef commented Jan 21, 2016

Yes, outside the template these are clearly working.
But do not actually work within the template neither with your last two solutions.
These solutions only mitigate the exception but do not succeed in modifying the parent template, that is the layout. What you are injecting is only valid in the PhpRenderer::render scope, but it gets lost when the ZendViewRenderer::renderModel loop iterates, eg. when it bubbles up to render the layout. So setting the layout in the template / PhpRenderer::render scope is not successful. Could you please validate this scenario?

Obviously this is not a blocking issue, but would be very elegant to be able to use it just like with the regular ZF2 MVC.

@weierophinney
Copy link
Member

@djozsef Ah, right; once you've called render(), if there is no root view model in place, then the current view model is root, which means no layout. As such, my first two were what you need to do. To recap:

  • provide a default layout in your configuration. If you do this, you can change the layout without an issue.

  • provide a layout when you call render(); this can be done by providing a layout variable, which can either be the string name of the layout to use, or the layout view model to use:

    $renderer->render('template::name', ['layout' => 'use::this::layout', /* other variables */]);
    $renderer->render('template::name', ['layout' => $layoutViewModel, /* other variables */]);

The takeaway is: you must have a layout in place before you call the helper in the template. Otherwise, it will raise an exception, as you've already noted.

Now, for the long explanation.

The reason is because once we pass off rendering to the PhpRenderer, all it does is look at the view model it has received. If that view model has children, it does a depth-first approach, rendering from the deepest child on out to the root view model. When you call layout() from any of the descendants, what the helper does is traverse back up the tree until it gets to the root view model, and then changes the template name on it.

In the case you've indicated, no layout was provided. This means that the current view model is the root, and when you call layout(), it cannot traverse upwards; there is no root view model at this time. As such, you've created an exceptional situation, and it properly errors.

Again, the takeaway: set a default layout in your configuration or provide a layout when you render.

Believe it or not, the only difference between this and the zend-mvc workflow is that in Expressive, we're not providing a default layout view model. The reason is simple: if we make that the default, then there's no way to render layout-less templates. A number of developers were quite interested in being able to do exactly that (e.g., to deliver rendered HTML snippets over XHR), which led to this implementation.

@djozsef
Copy link
Author

djozsef commented Jan 21, 2016

@weierophinney Thanks for summing up the full story. Given a deeper thought this is absolutely fine like this. Maybe we should include it in the docs.

@IgorDePaula
Copy link

Sotty, i try the expressive now, and this error occurred for me, but your informations and the readdocs not are clear for my, where make this modification? which files?

@geerteltink
Copy link
Member

@IgorDePaula Not sure if it helps you but the docs have moved to their final location at https://zendframework.github.io/zend-expressive/features/template/zend-view/#layouts

@djozsef
Copy link
Author

djozsef commented Feb 19, 2016

@IgorDePaula You cannot set the layout from within a template when using ZendViewRenderer. You have to pass the layout's name to the render() method along with the view data in the same structure (array or ViewModel). So in your middleware where you render the HTML output you would do something like this:

public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
{
    $viewData = array(
        "layout" => "templates/app/layout.phtml", 
        "title" => "Hello World Title", 
        "text" => "Lorem ipsum dolor sit amet ..."
    );
    $html = $renderer->render("templates/app/home.phtml", $viewData)
    return new HtmlResponse($html);
}

@IgorDePaula
Copy link

I written this form, but thw getting start recommend the sue of layout view
helper. This hour occurred thw error.
Em 19/02/2016 19:26, "József Dubravszky" notifications@github.com
escreveu:

@IgorDePaula https://github.com/IgorDePaula You cannot set the layout
from within a template when using ZendViewRenderer. You have to pass the
layout's name to the render() method along with the view data in the same
structure (array or ViewModel). So in your middleware where you render
the HTML output you would do something like this:

public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null){ $viewData = array( "layout" => "templates/app/layout.phtml", "title" => "Hello World Title", "text" => "Lorem ipsum dolor sit amet ..." ); $html = $renderer->render("templates/app/home.phtml", $viewData) return new HtmlResponse($html);}


Reply to this email directly or view it on GitHub
#68 (comment)
.

@IgorDePaula
Copy link

I do all on this page

https://zendframework.github.io/zend-expressive/getting-started/skeleton

and one hour I received error for getRoot not found for layout, and another hour I received error that view helper e is not registred.

@IgorDePaula
Copy link

I got now. I needed disable many settings.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

4 participants