Building ZF3 composed pages using Nesting View Models and Forward Controller Plugin

Normally in ZF3 MVC projects, each controller action matches one view and use it to render its output.

Occasionally, we may want to build your ZF3 pages by dispatching various controllers from within the matched controller and merging outputs into a unique final view. In this way we can aggregate one or more views to create complex pages like report summaries or widget dashboards.

In this post, we will see how to write few lines of code to include output of an arbitrary action into action matched from route.

So, we have one or more actions that perfectly work standalone as final pages, are fully functional and are rendered inside their standard layout. For example, we could have an Example action that shows some content like following:

Fig.1: Example action output as standalone page.

As you can see, it is a complete page with header, footer, etc.

Now, we decide to include this example action into our main page: most probably we want something that properly integrates into main page flow. The result of this operation should appear something like following image, where most of the page is generated by matched controller (indexAction of IndexController), while Example action section is a view resulting from an internally dispatched action:

Fig.2: Main page aspect. Red circle area contains output of Example action as child.

In this way, we will point to a main page and its controller will have to invoke Example action (or every action we feel necessary), will have to grab its output and finally will have to include this output into an arbitrary point of the page.

To achieve this goal, we can use a combination of Forward Controller Plugin and Nesting View Models practice (both from official ZF3 documentation).

As usual, let’s assume we just have a working Skeleton ZF3 Application. By connecting to index route (http://<server>:<port>/), we will see:

Fig.3: Default ZF3 Skeleton Application home page.

The first thing we have to do is to create an action to include into main page. We will implement exactly Example action shown in Fig.1. So, let’s add following method into IndexController:

module/Application/src/Controller/IndexController.php

class IndexController extends AbstractActionController
{

   ...

    public function toIncludeAction()
    {
        return new ViewModel();
    }
}

This action also need a view to render, so let’s create following template:

module/Application/view/application/index/to-include.phtml

<div class="jumbotron">
    <h1><span class="zf-green">Example</span> action</h1>

    <p>
        This action will be included as child into index action
        to show <span class="zf-green">addChild()</span> method usage.
    </p>

    <p><a class="btn btn-success btn-lg" href="https://daredevel.com" target="_blank">Give a look to Daredevel blog &raquo;</a></p>
</div>

At this point, we can browse to http://<server>:<port>/application/to-include and see same result shown in Fig.1

We will use indexAction into IndexController as main page, so we have to implement some code to invoke toIncludeAction (our Example action) and include its output, as shown in next snippet.

module/Application/src/Controller/IndexController.php

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        $viewModel = new ViewModel();

        $dispatchOutput = $this->forward()->dispatch(IndexController::class, array('action' => 'toInclude'));

        $viewModel->addChild($dispatchOutput, 'child1');

        return $viewModel;
    }

    public function toIncludeAction()
    {
        return new ViewModel();
    }
}

At line 7, we internally execute toIncludeAction by invoking dispatch() method of forward() plugin and passing it a controller identifier and an array containing a list of parameters (in our case, only action name).

At line 9, resulting output of dispatch(), stored into $dispatchOutput variable, is passed to addChild() method of ViewModel object. Second argument represent name assigned to children.

At this point, in view template, a variable correspondent to children name will be available to be rendered. We simply have to change it to echo $child1 variable as shown in line 15 of next snippet.

module/Application/view/application/index/index.phtml

<div class="jumbotron">
    <h1>Welcome to <span class="zf-green">Zend Framework</span></h1>

    <p>
        Congratulations! You have successfully installed the
        <a href="https://github.com/zendframework/ZendSkeletonApplication" target="_blank">ZF Skeleton Application</a>.
        You are currently running Zend Framework version <?= \Application\Module::VERSION ?>.
        This skeleton can serve as a simple starting point for you to begin
        building your application on ZF.
    </p>

    <p><a class="btn btn-success btn-lg" href="https://github.com/zendframework/zendframework" target="_blank">Fork Zend Framework on GitHub &raquo;</a></p>
</div>

<?= $child1 ?>

<div class="row">

    <div class="col-md-4">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">Follow Development</h3>
            </div>
            <div class="panel-body">
                <p>
                    Zend Framework is under active development. If you are
                    interested in following the development of ZF, you can check
                    <a href="http://framework.zend.com/blog/">ZF dev blog</a>,
                    and <a href="https://github.com/issues?utf8=%E2%9C%93&amp;q=is:issue+org:zendframework">ZF issue tracker</a>
                    (link requires a GitHub account). This is a great resource
                    for staying up to date with the latest developments!
                </p>

                <p><a class="btn btn-success pull-right" href="http://framework.zend.com/" target="_blank">ZF Development Portal &raquo;</a></p>
            </div>
        </div>
    </div>

    <div class="col-md-4">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">Discover Modules</h3>
            </div>
            <div class="panel-body">
                <p>
                    The community is working on developing a community site to
                    serve as a repository and gallery for ZF modules. The
                    project is available <a href="https://github.com/zendframework/modules.zendframework.com">on GitHub</a>.
                    The site is currently live and currently contains a list of
                    some of the modules already available for ZF.
                </p>

                <p><a class="btn btn-success pull-right" href="http://modules.zendframework.com/" target="_blank">Explore ZF Modules &raquo;</a></p>
            </div>
        </div>
    </div>

    <div class="col-md-4">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">Help &amp; Support</h3>
            </div>
            <div class="panel-body">
                <p>
                    If you need any help or support while developing with ZF,
                    you may reach us via IRC: <a href="irc://irc.freenode.net/zftalk">#zftalk on Freenode</a>.
                    We'd love to hear any questions or feedback you may have
                    regarding this release. Alternatively, you may subscribe
                    and post questions to the <a href="http://framework.zend.com/archives/">mailing lists</a>.
                </p>

                <p><a class="btn btn-success pull-right" href="http://webchat.freenode.net?channels=zftalk" target="_blank">Ping us on IRC &raquo;</a></p>
            </div>
        </div>
    </div>
</div>

Result of this rendering (still available at http://<server>:<port>/) will be the same as in Fig.2.

Articoli correlati