Creating View Helper

Creating View Helper

Need some way to get a list of recent articles in the footer.

I need to build a list of recent articles from the model/database and display it in the footer section of my layout. It means I need to pass an array object variable to the layout containing article items and I found several ways to achieve this.

  1. Setting a layout variable in the controller action.
# /module/Application/src/Controller/IndexController.php

<?php
namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        /**
         * $articles is the array of article entities retrieved somehow
         */
        $articles = [];
        for ($i = 0; $i < 5; $i++) {
            $article = new \stdClass;
            $article->title = sprintf('Article %d', $i);
            $article->url = sprintf('/articles/%d/', $i );
            $articles[$i] = $article;
        }

        $this->layout()->setVariable('articles', $articles);
       
        return new ViewModel();
    }
}

And how I use it in the layout.

# /module/Application/view/layout/layout.phtml

<?php
    // ...

    <footer>
        <ul>
        <?php foreach ($this->articles as $article): ?>
            <li><a href="<?= $article->url ?>"><?= $article->title ?></a></li>
        <?php endforeach; ?>
        </ul>
    </footer>
   
    // ...

This is good if I need the layout variable for only specific controllers' actions. However, I need it all the time as I will put the recent articles list in the footer.

  1. In the Module.php onBootstrap
# /module/Application/src/Module.php

    // ...
   
    public function onBootstrap($e) 
    {
        $serviceManager = $e->getApplication()->getServiceManager();
        $viewModel = $e->getApplication()->getMvcEvent()->getViewModel();

	    /*
	     * $articles is the array of article entities retrieved somehow
	     */
        $articles = [];
        for ($i = 0; $i < 5; $i++) {
            $article = new \stdClass;
            $article->title = sprintf('Article %d', $i);
            $article->url = sprintf('/articles/%d/', $i );
            $articles[$i] = $article;
        }
              
        $viewModel->articles = $articles;
    }
    
    // ...
    

This looks to be a good way, however, I will still learn how to create a view helper to be able to use it in all views.

  1. A View Helper that will work in any view including layout!

I create a folder for the view helper at /modules/Application/src/View/Helper/ and the actual RecentArticlesHelper.php file in it:

# /module/Application/src/View/Helper/RecentArticlesHelper.php

<?php
namespace Application\View\Helper;

use Zend\Form\View\Helper\AbstractHelper;

class RecentArticlesHelper extends AbstractHelper 
{
    public function __invoke() 
    {
        /**
         * $articles is the array of article entities retrieved somehow
         */
        $articles = [];
        for ($i = 0; $i < 5; $i++) {
	        $article = new \stdClass;
            $article->title = sprintf('Article %d', $i);
            $article->url = sprintf('/articles/%d/', $i );
            $articles[$i] = $article;
        }
	    
        return $articles;
    }
}

I have to introduce my view helper to my application by adding it into the configuration array.

# /module/Application/src/config/module.config.php

<?php
namespace Application;

use Zend\ServiceManager\Factory\InvokableFactory;

return [
    // ...
    
    'view_helpers' => [
        'aliases' => [
            'recentarticles' => View\Helper\RecentArticlesHelper::class,
        ],
        'factories' => [
            View\Helper\RecentArticlesHelper::class => InvokableFactory::class,
        ],
    ],
    
    // ...
];

And slightly different usage in the layout.

# /module/Application/view/layout/layout.phtml

<?php
    // ...

    <footer>
        <ul>
        <?php foreach ($this->recentarticles() as $article): ?>
            <li><a href="<?= $article->url ?>"><?= $article->title ?></a></li>
        <?php endforeach; ?>
        </ul>
    </footer>
   
    // ...

Ok, I have a fully functional view helper but articles are supposed to be coming from the model. I need to use article mapper - which is not detailed here - to retrieve article items. Therefore I need to inject mapper into the view helper by using a factory class. I create /module/Application/src/View/Helper/Factory/ folder and actual RecentArticlesHelperFactory.php file in it.

# /module/Application/src/View/Helper/Factory/RecentArticlesHelperFactory.php

<?php
namespace Application\View\Helper\Factory;

use Zend\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;

use Application\Model\Article\ArticleMapper;
use Application\View\Helper\RecentArticlesHelper;

class RecentArticlesHelperFactory implements FactoryInterface 
{

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $articleMapper = $container->get(ArticleMapper::class);
    
        $viewhelper = new RecentArticlesHelper($articleMapper);
    
        return $viewhelper;
    }    
}

I should also change the view helper class to get this mapper through its constructor. Here is the revised view helper class.

# /module/Application/src/View/Helper/RecentArticlesHelper.php

<?php
namespace Application\View\Helper;

use Zend\Form\View\Helper\AbstractHelper;
use Application\Model\Article\ArticleMapper;

class RecentArticlesHelper extends AbstractHelper 
{
    /**
     *
     * @var ArticleMapper $articleMapper
     */
    protected $articleMapper;
    
    /**
     * 
     * @param ArticleMapper $articleMapper
     */
    public function __construct(ArticleMapper $articleMapper)
    {
        $this->articleMapper = $articleMapper;
    }
    
    
	public function __invoke() 
	{
	    /**
	     * ArticleMapper::fetchAll method takes 
	     * itemsPerPage parameter to limit returned items by the model
	     * orderField and orderDirection for ordering the results
	     * I want to retrieve only 5 items as seen in the call
	     * and order descending by publish date to retrieve the most recent articles
	     */	    
	    $articles = $this->articleMapper->fetchAll(5, 'publish_date', 'desc');
	    
	    return $articles;
	}
}

It is a good time to inform my application that I am now creating the helper by using the factory.

# /module/Application/src/config/module.config.php

<?php
namespace Application;

return [
    // ...
    
    'view_helpers' => [
        'aliases' => [
            'recentarticles' => View\Helper\RecentArticlesHelper::class,
        ],
        'factories' => [
            View\Helper\RecentArticlesHelper::class => View\Helper\Factory\RecentArticlesHelperFactory::class,
        ],
    ],
    
    // ...
];

Finally, I will change the layout code to get actual entity properties via getters instead of previously used stdClass properties listed for demo purposes. Again, the article entity is also not detailed here just like the mapper.

# /module/Application/view/layout/layout.phtml

<?php
    // ...

    <footer>
        <ul>
        <?php foreach ($this->recentarticles() as $article): ?>
            <li><a href="<?= $article->getUrl() ?>"><?= $article->getTitle() ?></a></li>
        <?php endforeach; ?>
        </ul>
    </footer>
   
    // ...


Published on Dec 02, 2016