I am trying to nest two (or more) views using the following code. I am struggling to find a way to successfully nest these views without losing the final view content and passing it through the $this->content
variable within the last layout, as it just returns an empty string.
core/Framework/Mvc/Controller/BaseActionController.php
This is a simple base controller which uses the $frame and $layout variables (so that they can be used within any controller extending this class). The idea is the frame is defined as the page starting with <!DOCTYPE html>
and the layout is the HTML which gets displayed in the frame using <?= $this->content; ?>
.
namespace Framework\Mvc\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class BaseActionController extends AbstractActionController
{
protected $frame;
protected $layout;
protected $layouts = array();
public function preDispatch() {...}
public function dispatch() {..}
public function postDispatch()
{
if ($this->frame !== null) {
$this->layouts[] = $this->frame;
}
if ($this->layout !== null) {
$this->layouts[] = $this->layout;
}
foreach ($this->layouts as $layout) {
$view = new ViewModel();
$layoutView = new ViewModel();
$layoutView->setTemplate($layout);
$layoutView->addChild($view);
}
}
}
module/Application/view/layout/frame.phtml
The <?= $this->content; ?>
part within this template should echo out the layout.phtml template along with it's own <?= $this->content; ?>
.
<?= $this->doctype(); ?>
<html>
<head>
<meta charset="utf-8">
<title>Woohoo, I'm a frame</title>
</head>
<body>
<?= $this->content; ?>
</body>
</html>
module/Application/view/layout/admin/layout.phtml
The $this->content
variable should echo out the contents of the module/Users/view/users/test/index.phtml
file. At this point, the variable returns an empty string.
<header>
<img class="logo" src="<?= $this->basePath() ?>/img/logo.png" alt="Company">
<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>
<section>
<?= $this->content; ?>
</section>
<footer>
<ul>
<li><a href="#">Copyright</a></li>
<li><a href="#">Sitemap</a></li>
<li><a href="#">Privacy policy</a></li>
</ul>
</footer>
module/Users/view/users/test/index.phtml
<h1 class="page__title">Test</h1>
<p class="page__content">The final view</p>
Temporary solution (not very nice to write this in each action)
<?php
namespace Users\Controller;
use Framework\Mvc\Controller\BaseActionController;
use Zend\View\Model\ViewModel;
class TestController extends BaseActionController
{
public function indexAction()
{
$view = new ViewModel();
$view->setTemplate('users/test/index.phtml');
$adminView = new ViewModel();
// This layout is defined in the Application module.config.php file
$adminView->setTemplate('layout/admin');
$adminView->addChild($view);
return $adminView;
}
}
As above, my temporary solution is to choose the template the ViewModel()
instance needs, manually. I notice $view->setTemplate();
works but without defining one, $view->getTemplate();
returns an empty string. I am not sure where, in Zend Framework 2, the default template is being defined so I can replicate this within the base controller.
I think the solution I have (temporarily) could work, the only issue being the manual $view->setTemplate('/path/to/my/template.phtml');
. If I can replicate how Zend does this, then it should work correctly but I am at a loss passing the $this->content
variable into the layout.phtml
file with the contents being the final view.
UPDATE:
As suggested by Next Developer, I have added the following:
module/Application/Module.php
<?php
namespace Application;
use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;
use Zend\Session\Container;
use Framework\Mvc\View\Http\TemplateInjector;
class Module
{
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$request = $app->getRequest();
$response = $app->getResponse();
$eventManager = $app->getEventManager();
$serviceManager = $app->getServiceManager();
$session = new Container('locale');
if (!$session->offsetExists('locale')) {
$session->offsetSet('locale', \Locale::acceptFromHttp($request->getServer('HTTP_ACCEPT_LANGUAGE')));
}
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$serviceManager->get('translator')
->setLocale($session->locale)
->setFallbackLocale('en_GB');
$eventManager->getSharedManager()
->attach(
'Zend\Stdlib\DispatchableInterface',
MvcEvent::EVENT_DISPATCH,
new TemplateInjector(),
-80
);
}
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
}
core/Framework/Mvc/View/Http/TemplateInjector.php
<?php
namespace Framework\Mvc\View\Http;
use Zend\Mvc\MvcEvent;
use Zend\View\Model\ModelInterface as ViewModel;
class TemplateInjector
{
public function __invoke(MvcEvent $event)
{
$model = $event->getResult();
if (!$model instanceof ViewModel) {
return;
}
if ($model->getTemplate()) {
return ;
}
$controller = $event->getTarget();
if (!is_object($controller)) {
return;
}
// @todo: Clear this mess up
$namespace = explode('\\', ltrim(get_class($controller), '\\'));
$controllerClass = array_pop($namespace);
array_pop($namespace);
$moduleName = implode('/', $namespace);
$controller = substr($controllerClass, 0, strlen($controllerClass) - strlen('Controller'));
$action = $event->getRouteMatch()->getParam('action');
$model->setTemplate(strtolower($moduleName.'/'.$controller.'/'.$action.'.phtml'));
}
}
Any changes in the TemplateInjector doesn't seem to change the view, by this time it seems too late. It does however set the template on the view. When making a new instance of $view = new VidewModel();
it uses the template defined in the TemplateInjector class which should allow me to automate the layout process, but the scope of everything being set, it seems too late. I know I can access the controller, the view and the model in the TemplateInjector but no matter how I change the views or add children, it doesn't come out on the front end. If anyone could provide a working example, that would be really helpful.