Manually render separate file (template)

How to manually render some single file and get result contents inside controller? For example, I have an registration email in html format. Before the sending I want add there some a data like a user name or something else. So I think the better way is to use the view engine I already have. But I have problems during realization.

At first I must say that action which sends email doesn't provide any view (email sends from inner action and does redirect to another route). So current view could be replaced with anything.

Problems I have: — The dump of View::partial() is always null, but not a string as it should. So I can't use gotten content. But at the same time partial() performed well inside controller without any 'echo' for it and add gotten data to the final view. — The dump of View::getContent() is always null too, so I can't use pick() or something because can't get result content. — Seems like stand-alone doesn't work properly without some render(). But there is only one file for my case and there is nothing to put into render().



82.7k

You can use getRender:

$content = $this->view->getRender('emails', 'html-template'); // apps/views/emails/html-template.html or apps/views/emails/html-template.volt


20.7k
edited Mar '14

The problem is that getRender() renders all levels forced. Even so

$view->setRenderLevel(View::LEVEL_NO_RENDER);
echo $this->view->getRender('emails', 'signup-html');

I got my 'signup-html' within the /views/index.phtml. Also, if some /layouts/emails.phtml exists, it will be used for render too.

I just want to directly transform some single *.phtml, based on Phalcon\Mvc\View or Phalcon\Mvc\View\Engine\Volt capabilities, into simple ready-to-use html.



82.7k

You could clone the view component before call getRender:

$view = clone $this->view;
$view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$content = $view->getRender('emails', 'signup-html');


20.7k
edited Mar '14

Sadly, but it produce the same result as above. Maybe it is a bug, because $view->render() really shows the actually chosen by setRenderLevel() level. But getRender() just ignores it and render all levels. If I do this:

$view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$content = $view->render('emails', 'signup-html');

I will get exactly only "/views/email/signup-html.phtml" rendered and outputted, other words — an action view level. But will outputted immediately and also $content == null. I wish I have the same result without an outputting and with a returned string. From your words I see the getRender() has to doing this, but it doesn't =(

A such way could be used:

ob_start();
$this->view->render('emails', 'signup-html');
$content = ob_get_clean();

but in some sense it breaks the conception of framework.



82.7k

The following code produce the expected result:

$view = clone $this->view;
$view->start();
$view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$view->render('emails', 'signup-html');
$view->finish();
$content = $view->getContent();


20.7k

Yeah, this is it =) Thanks!

edited Mar '14

But this solution break my frontend layout.

From my controller action:

$view = clone $this->view;
$view->start();
$view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$view->render('emails', 'signup-html');
$view->finish();
$content = $view->getContent();

// SEND $content as email
// swiftmailer, see example at http://www.sitepoint.com/sending-confirmation-emails-phalcon-swift/
// ... that works fine

// Forward to "message/show"
$this->flash->success('ok ok ok');
return $this->dispatcher->forward( array('controller' => 'message',  'action' => 'show') );

Without the "$view clone render" part, the last line "dispatcher forward" work and show me the right layout (but then the email is empty).

When i use your example i got only an empty screen with my success message "ok ok ok", but nothing else, the layout and everything else is missing.

With or without the forward is not important, no layout is visible in both cases.



82.7k
edited Mar '14

Just remove this line:

 $view->setRenderLevel(View::LEVEL_ACTION_VIEW);

if you want to include the layout

edited Mar '14

But then i have the full layout in the email, but my frontend is still empty.

The only thing i can do is a redirect (with a session message)

$this->response->redirect('message/show');

Is that a bug, because the view is cloned ?



19.1k

You could create a class like this in your library:

<?php
namespace My;

use Phalcon\Mvc\View\Engine\Volt\Compiler;

class Template
{
    var $compiledPath;
    var $templatesDir;
    var $compiler;

    function __construct($compiledPath, $templatesDir)
    {
        $this->compiledPath = $compiledPath;
        $this->templatesDir = $templatesDir;

        $this->compiler = new Compiler();

        $this->compiler->setOptions(array(
            "compiledPath" => $this->compiledPath,
        ));
    }

    public function getString($templatePath, $data)
    {
        extract($data);

        $this->compiler->compile($this->templatesDir . '/' . $templatePath);

        ob_start();

        include $this->compiler->getCompiledTemplatePath();

        return ob_get_clean();
    }
}

then register it as a service:

$di->set('template', function() use ($config) {
    $template = new My\Template($config->application->cacheDir, $config->application->templatesDir);
    return $template;
});

then in your controllers you can just call it like this:

 $string = $this->template->getString('some-template.volt', array(
            'name' => 'John',
            'age' => '521'
        ));

Any suggestions to imporve this code is welcomed.



6.8k
edited Sep '14

Hi.

I made sotthing like that In My Controller Class

<?php
    $this->view->start();
    $this->view->render("user_experiences/ang-templates", 'template-name');
    $this->view->finish();
    ....
    $data = array(
        'content-of-template' => $this->view->getContent()
    );

in my index.php

    $application = new \Phalcon\Mvc\Application($di);

    $application->useImplicitView(false);

    echo $application->handle()->getContent();

!IMPORTANT After these changes you have to manually add the next code in each controller's method where you want print a content

class AppController extends Controller
{
    public function someAction()
    {
        ...

        $this->view->render('app', 'some', array('var' => 'somevar'));
    }
}

But I still do not like this solution. It's strange that method $this->vew>render() prints a content instead of returning it to variable. Also it's very strange that this simple action, get partials/template, force us make such strange movements/



19.1k
edited Oct '14

Just wanted to share with you guys another way I managed to achieve this using Phalcon\Mvc\View\Simple. You can use it to render simple template files when you don't need view hierarchy. This way the template you want to render won't interfere with your main application layout.

$di->set('simpleView', function() use ($config) {
    $view = new Phalcon\Mvc\View\Simple();
    $view->setViewsDir($config->application->viewsDir);
    return $view;
});

Then call it like this:

$this->di['simpleView']->render("view/template/name", array(
            'variable1' => $foo,
            'variable2' => $bar
        ));

Check http://docs.phalconphp.com/en/latest/api/Phalcon%5Mvc%5View%5Simple.html for more information

edited Oct '14

This still doesnt work for me ...

Scenario :

  1. i create a twitter bootstrap modal for user to register
  2. once he fills in the details, I ajax it to the server, to user module, to save
  3. once the user is saved (created), i want to render email (volt template with text and some variables) and send it
  4. normal "thank you for your registration" view is rendered and send back to the modal

what doesnt work here is the part 3-4. If I manage to render the email template via VIEW/SIMPLE class, the normal app view (step 4) ends up empty. And vice versa, if step 3 fails, the 4 is redendered correctly and the message shows up.

Main parts :

index.php

$di->set('simpleView', function() use ($config) {
  $simpleView = new Phalcon\Mvc\View\Simple();
  $simpleView->setViewsDir($config->application->viewsDir);
  $simpleView->registerEngines(array(".volt" => "Phalcon\Mvc\View\Engine\Volt"));
  return $simpleView;
}, true);

Mail.php

public function getTemplate($name, $params)
{
    return $this->simpleView->render("emails/" . $name, $params);
}

Users.php

$this->view->pick("user/register_ok");
$this->view->setRenderLevel(Phalcon\Mvc\View::LEVEL_ACTION_VIEW);

PS. sorry for non-formatting, I tried to add the code formating like twenty times, no success, markdown doesnt work for me here

if I change the email template from .volt to .phtml (and modify the simpleView class accordingly), it works without problem. Why is it exclusive to each other ?



19.1k
edited Oct '14

The email rendering shouldn't interfere with your normal app view, you must be doing something wrong. In any case, you can register both .volt and .phtml like this:

$di->set('simpleView', function() use ($config) {
    $view = new Phalcon\Mvc\View\Simple();
    $view->setViewsDir($config->application->viewsDir);
    $view->registerEngines(array(
        ".phtml" => "Phalcon\Mvc\View\Engine\Php",
        ".volt" => "Phalcon\Mvc\View\Engine\Volt"
    ));
    return $view;
});

I did that, if I leave only .volt there, it works for email, but doesnt work for the normal view template :( It seems to me that once I use .volt template, the other .volt template is not rendered. Not sure where I did go wrong.



19.1k

Are you getting any errors?



19.1k
edited Oct '14

I did a test and indeed there is something odd, but only when you register the engines using the class name. I'm not sure why, the container must be sharing something. I will try to check on that later,anyone has any idea? That said, if you register the engines like the following it works. Give it a try and let me know:

$di->set('simpleView', function() use ($config) {
    $view = new Phalcon\Mvc\View\Simple();
    $view->setViewsDir($config->application->viewsDir);
    $view->registerEngines(array(       
        '.phtml' => function ($view, $di) use ($config) {
            $phtml = new \Phalcon\Mvc\View\Engine\Php($view, $di);
            return $phtml;
        },      
        '.volt' => function ($view, $di) use ($config) {
            $volt = new  \Phalcon\Mvc\View\Engine\Volt($view, $di);           
            return $volt;
        }
    ));
    return $view;
});

you are right, this works for me too, great, thanks a lot !!!

just an idea ... in my index.php file in root of the public dir, there is following code

<?php
    /**
     * Setting up volt
     */
    $di->set('volt', function($view, $di) {

        $volt = new \Phalcon\Mvc\View\Engine\Volt($view, $di);

        return $volt;
    }, true);

might this be a problem ? I guess setting the $volt and binding it to a view, when there will be two views ($view, $simpleView) working with it may cause this issue ? just a guess



19.1k

The problem occours when two view components share the same engine instance. I'm not sure why you are setting the volt engine directly like this, but if it's being used by more than one view component then you probably will have a conflict. That's why using a closure works, like the last example I posted. You need to create a new instance of the engine for each view component.

The problem occours when two view components share the same engine instance. I'm not sure why you are setting the volt engine directly like this, but if it's being used by more than one view component then you probably will have a conflict. That's why using a closure works, like the last example I posted. You need to create a new instance of the engine for each view component.

Half an hour triying to fix this. My Volt Engine was set as shared on the dic and both view (simple and normal) where using it. The simple view did no show any render until I set the voltengine as no shared. :( That was difficult!