When sending out e-mails in Zend Framework 2, I want to use view script templates such that I can leverage a layout for my various e-mail templates. The idea is that I have a layout which has the shared markup and echoes out content somewhere. Exactly how layouts work in general. To accomplish this, I am trying to add a child view model to my layout view model and render it with the PhpRenderer.
public function someMethod() {
$child = new ViewModel();
$child->setTemplate('test-template');
$layout = new ViewModel();
$layout->setTemplate('email-layout');
$layout->addChild($child, 'content');
$phpRenderer = new \Zend\View\Renderer\PhpRenderer();
$phpRenderer->setCanRenderTrees(true);
$resolver = new \Zend\View\Resolver\TemplateMapResolver();
$resolver->setMap($this->serviceLocator->get('ViewTemplateMapResolver'));
$phpRenderer->setResolver($resolver);
$output = $phpRenderer->render($layout);
}
Layout view script:
Hello from the e-mail layout script!
<?php echo $this->content; ?>
What happens is that only the layout's content is stored in $output and $this->content within the layout view script is NULL.
What I am trying to do is to store the HTML in a string such that I can send it in an e-mail. I instantiate a child view model, which represents the view script that I would like to render within the layout. Then I instantiate the layout view model and add the child view model. Then I create a PhpRenderer and add a TemplateMapResolver to it such that it will resolve templates by using the template map. Lastly, I call the render method on the PhpRenderer which takes a ViewModel as its first parameter. I have added both templates to my template_map configuration and the template map is set correctly with the correct names and correct paths.
The thing is that I do not see any handling of child views in its render method. This makes me think if I am using the PhpRenderer class incorrectly and perhaps that is why it doesn't work. I have checked the documentation for nesting view models, and I am doing the same thing, except not returning the view model from a controller action. If I return $layout from a controller action, it works.
If I look at Zend\View\View's render method, I can see that it renders child views. It would seem as if I cannot use the PhpRenderer like I want to. But can I use the Zend\View\View for this? The render method does not return anything, but triggers some events. I am not too familiar with the event manager yet, so I am not sure how/if I can capture the output if using this method. Would it be sensible to listen to the response event? I would have to check which view was rendered I guess (or detach my listener after doing my work). I don't know if that's any good.
Any ideas are more than welcome. Thank you!
$this->content is null because PhpRenderer just render self level not including children view models.
Actually the ZF2 MVC rendering are using Zend\View\View->render() but not Zend\View\Renderer\PhpRenderer->render(), so you need to render tree templates exactly like what Zend\View\View does:
foreach ($layout as $child) {
if ($child->terminate()) {
continue;
}
$child->setOption('has_parent', true);
$result = $view->render($child);
$child->setOption('has_parent', null);
$capture = $child->captureTo();
if (!empty($capture)) {
if ($child->isAppend()) {
$oldResult=$model->{$capture};
$layout->setVariable($capture, $oldResult . $result);
} else {
$layout->setVariable($capture, $result);
}
}
}
$output = $view->render($layout);
See ZF2 source code to know more:
https://github.com/zendframework/zf2/blob/master/library/Zend/View/View.php#L222
Related
I have an MVC system which serves my web content through a URI. (www.something.com/view/me) and controllers serve a model and then a view.
Sometimes, I have content which requires client side interaction before it can generate content. In this case, a search based on location which needs the client to provide the location. Therefore I am calling the page, then calling a different URI using ajax and retrieving the html content to place on the page.
This works when I place the php/html directly in a model and echo it. I then use javascript in the ajax call to fill the void with the new content. But this is not neat. I would rather have all my presentation (even the dynamically generated stuff) in my views. How can I change the view class in my MVC to allow this?
I should point out that my current view class uses 'require' to output the view. However this i not suitable for ajax returns. Only a direct output will work.
I have tried file_get_contents('/view/generated_view') with a file location but this outputs raw code with no php parser in the flow. (I have also tried to run it as a direct url file_get_contents('www.something.com/view/generated_view') but the rules of MVC don't allow direct access to views.) I have also tried eval() but again, this has not worked and I want to avoid.
I will carry on and use the model to generate the html for output but is there something I am missing? a method I have not thought of?
You could use something like this.
class View {
protected $template;
protected $viewName;
protected $params;
public function __construct($viewName, $params = array()){
$this->viewName = $viewName;
$this->params = $params;
$this->render();
}
protected function render(){
if(empty($this->viewName)){
throw new Exception("ERROR: the view name is empty.");
}
$this->template = $this->getContentTemplate();
echo $this->template;
}
protected function getContentTemplate()
{
$templatePath = TEMPLATE_PATH . "/{$this->viewName}.php";
if(!is_file($templatePath)){
throw new Exception("ERROR: The template {$this->viewName} do not exists.");
}
$view = (object) $this->params; // the object $view can be used directly in the template
ob_start(); // opens output buffer
require($templatePath); // require template file, it's hold on the buffer
$template = ob_get_contents(); // get the buffer content
ob_end_clean();
return $template;
}
}
It´s a simple view class but you'll get it.
I'm trying to create my own little PHP-Framework just for fun and to learn.
But now I stuck with the View.
class Index extends Controller {
function __construct() {
parent::__construct();
$this->view->msg = 'This message is sended over the view.';
$this->view->content = 'This is the INDEX-Content.';
$this->view->render('index/index');
}
public function something() {
// do something
// and render it
$this->view->content = 'This is the content from something.'
}
So what I can do is to misuse the __destruct and render here my output. But I guess that is against the purpose of this method.
When I compare my intention with e.g. Zend Framework or Laravel they use e.g. an after() method to render a view.
But I do not understand which method can do this. The constructor is the first, the destructor the last and everything between it has to be called to work.
Are there any "magic" methods for this?
You should handle your HTTP I/O
This is how you can output
This is how a request is executed
This is how the action is triggerd
Sniff through the repo as much as you can, Kohana is a simple yet powerfull framework. (you can learn a thing or two)
You can do something like this in your main Controller class :
public function __call($method,$arguments) {
if(method_exists($this, $method)) {
call_user_func_array(array($this,$method),$arguments); //this is where the function is called
$this->render();
}
}
You can eliminate in hear constructors, destruct and other functions that you do not want to automatically render.
You can also have a variable in your main Controller class, autoRender set default to false and just set it to true when you want to produce a predefined output.
Also in the _call function, you can use the $method variable to have a predefined name for your view. Like for example lets say you would have a folder in your framework called Views and in there you would have a file called something.view_extension.
You can send to render like this : $this->render($method.'.view_extension');
Just a bulk idea you can work around. :)
Before I converted my Zend Framework application to a module based structure, it had a single layout and I could pass variables to it from my controller like so:
// Controller action
$this->view->foo = 'Something';
// Layout
<?= $this->foo ?>
However, since moving everything into a default module and creating a separate "admin" module, I can no longer get this to work, most likely due to me moving my "View Settings" out of my bootstrap file and into the controller plugin where I am switching the layout based on the module. My plugin looks like this:
class KW_Controller_Plugin_LayoutSelector extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$layout = Zend_Layout::getMvcInstance();
$layout->setLayout($request->getModuleName());
$view = $layout->getView();
$view->doctype('HTML5');
$view->setEncoding('UTF-8');
$view->headMeta()->appendHttpEquiv('Content-Type', 'text/html; charset=UTF-8');
$view->headScript()->appendFile('/js/jquery-1.7.1.min.js');
switch ($request->getModuleName()) {
case 'admin':
$view->headTitle('Admin Area')->setSeparator(' - ');
$view->headLink()->appendStylesheet('/css/admin/global.css');
$view->headScript()->appendFile('/js/admin/common.js');
break;
default:
$view->headTitle('Main Site')->setSeparator(' - ');
$view->headLink()->appendStylesheet('/css/global.css');
$view->headScript()->appendFile('/js/common.js');
break;
}
}
}
If I move all of those method calls to the view back into my bootstrap, I can pass variables to the layout again; so I'm guessing that something is happening in the wrong order, maybe my layout is being switched after I have passed the variables to the view from my controller and they're not making it into my layout? (I've tried changing the point at which the above runs by putting the code in both preDispatch() and postDispatch(), etc.)
It's worth noting, that I can access these variables in my individual view scripts, just not the layout that they're contained within.
Any pointers would be greatly appreciated.
I'm doing something similar in a current project and it's largely working fine.
One difference I note is that I am bootstrapping both Layout and View at the app-level using the standard application resources.
Then in my plugin, I access the view using:
$front = Zend_Controller_Front::getInstance();
$view = $front->getParam('bootstrap')->getResource('view');
This seems to guarantee I am accessing the same view instance at bootstrap, in plugins, and in controllers.
Maybe this will not solve the whole problem but it will help a bit with this ugly swith You have there :-)
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$module = $request->getModuleName();
$layout = Zend_Layout::getMvcInstance();
// check module and automatically set layout
$layoutsDir = $layout->getLayoutPath();
// check if module layout exists else use default
if(file_exists($layoutsDir . DIRECTORY_SEPARATOR . $module . ".phtml")) {
$layout->setLayout($module);
} else {
$layout->setLayout("layout");
}
}
}
I'm using the MVC pattern in my application.
Now I need the view object in a model.
I don't want to add the view as a parameter for my function in the model (since I need it in other functions as well). And I don't want to keep on passing it.
Should a add the view as an attribute for the constructor of the model?
Is there another way? Shouldn't I be needing the view object in the model in the first place?
What would be the preferred way of doing it?
Example:
Controller
function someAction()
{
$somemodel->add();
}
Model
class SomeModel()
{
function add()
{
if ($view->user) {
// do stuff
$this->mail();
} else {
// do other stuff
}
}
function mail()
{
Mailer::send($view->user->email, $this->getitems(), $view->layout);
}
function getitems()
{
return Items::getitems($view->user);
}
}
If you're really doing MVC, then you won't need the view in the model, because only the controller should have access to the view.
Looking at the code you've provided, I can tell one thing: the add() method should not reference $view in any way (even for accessing its properties). Instead, the model should be provided with the $view->user value from the controller. The same goes for the mail() method.
Consider fixing those issues. Otherwise, you'll get into something worse later on.
The model should be separate from the view. So, as mkArtak said, the controller should be the only thing that communicates with the view. Which then passes only the necessary information to the model.
As for the model, it should really only deal with the information that it understands.
i.e. if you had a Car model... you don't want to build it dependent on it's factory. If you did, you would have to change your code if you wanted to build it in different factory.
The controller is where you 'bake' everything prepare for render. By bake I mean you consider any passed in $_REQUEST params, make model API calls to get the data you need, and set template variables to be rendered. Your action, at the end of this process should make a call to a template (view) you choose in order to render the 'baked' template variables.
Well, this is a tricky one, and I'm not really sure it's not breaking the MVC model.
I'm loading some data into the controller, retrieved from the model. I'm passing this object to the view almost in every action. I'm processing this data from a helper and I'm passing the object as an argument:
controller:
$this->('section', $section);
helper:
<h3><?php echo $parser->section_name($section); ?></h3>
However, I think it would be way better if I could pass that $section object as a private variable inside the parser helper. I could do this in the first line of each view:
$parser->section_object = $section;
And each parser method will look something like
function section_name(){
return $this->section_object['Section']['name'];
}
The question is: is there a way to automatizate this from the controller? Because the controller can't access the helper, I tried creating the helper from the controller and setting the local variable there:
function beforeFilter(){
$section = $this->Section->getOne();
App::import('Helper', 'Parser');
$ParserHelper = new ParserHelper();
$ParserHelper->section_object = $section;
$this->set('parser', $ParserHelper);
}
However, if the helper includes some other helpers, they won't be loaded and the helper will trigger a lot of errors.
Thanks.
You have to manually create the helpers used by your helper. For example, if your helper uses the HtmlHelper, you have to do something like:
App::import('Helper', 'Html');
$ParserHelper->Html = new HtmlHelper();