Welcome to Abdul Malik Ikhsan's Blog

Zend Framework 2 : Step by Step Create Custom View Strategy

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on December 10, 2012

zf2-zendframework2 Zend Framework 2 ships with three Rendering and Response Strategies that we can use within our application : PhpRendererStrategy, JsonStrategy, and FeedStrategy. If we want to use a custom strategy, for example : add content-type : image/png to all header response, we should create custom view strategy to create custom response. I will give you an example how to create it.


1. create a view strategy

namespace YourModule\View\Strategy;

use Zend\EventManager\ListenerAggregateInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\Mvc\MvcEvent;
use Zend\View\Renderer\RendererInterface;
use Zend\View\ViewEvent;
use Zend\View\Model\ViewModel;
 
class ImageStrategy implements ListenerAggregateInterface
{
    protected $renderer;
    protected $listeners = array();
    
    public function __construct(RendererInterface $renderer)
    {
        $this->renderer = $renderer;
    }
    
    public function attach(EventManagerInterface $events, $priority = 1)
    {                               
       $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, array($this, 'selectRenderer'), $priority);
       $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, array($this, 'injectResponse'), $priority);
    }
    
    public function selectRenderer(ViewEvent $e)
    {
        return $this->renderer;
    }
    
    public function injectResponse(ViewEvent $e)
    {
        $renderer = $e->getRenderer();
        if ($renderer !== $this->renderer) {
            return;
        }

        $result   = $e->getResult();
        
        $response = $e->getResponse();
        $response->setContent($result);
        $headers = $response->getHeaders();
        $headers->addHeaderLine('content-type', 'image/png');
    }
    
    public function detach(EventManagerInterface $events)
    {
        foreach ($this->listeners as $index => $listener) {
            if ($events->detach($listener)) {
                unset($this->listeners[$index]);
            }
        }
    }
}

2. Create Factory

namespace YourModule\View\Strategy;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class ImageFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $viewRenderer = $serviceLocator->get('ViewRenderer');
        return new ImageStrategy($viewRenderer);
    }
}

3. Register in service manager

return array(
        'factories' => array(
            'ImageStrategy' => 'YourModule\View\Strategy\ImageFactory',
        )
);	

4. Register in view_manager in your module.config.php

'view_manager' => array(
       'strategies' => array(
           'ImageStrategy'
        ), 
 ),

Done !

What if we need the custom strategy is for specific module ? This is it :

namespace YourModule;

use Zend\Mvc\MvcEvent;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
	$sharedEvents        = $e->getApplication()->getEventManager()->getSharedManager();
	$sm = $e->getApplication()->getServiceManager(); 
	
	$sharedEvents->attach(__NAMESPACE__, MvcEvent::EVENT_DISPATCH, function($e) use ($sm) {
	    $strategy = $sm->get('ImageStrategy');
	    $view     = $sm->get('ViewManager')->getView();
	    $strategy->attach($view->getEventManager());
        }, 100);
    }
    
    public function getServiceConfig()
    {
	return array(
                    'factories' => array(
                	'ImageStrategy' => 'YourModule\View\Strategy\ImageFactory',
                    )
        );
    }
    
    public function getAutoloaderConfig(){ /*common code */ }
    public function getConfig(){ /*common code */ }
}

Testing, read image file from your controller :

namespace YourModule\Controller;

use Zend\Mvc\Controller\AbstractActionController;

class SkeletonController extends AbstractActionController
{
   public function fooAction()
    {
        return readfile('./public/images/zf2-logo.png');
    }
}

Reference :
1. http://zf2.readthedocs.org/en/latest/modules/zend.view.quick-start.html
2. http://juriansluiman.nl/en/article/119/attach-a-view-strategy-for-specific-modules

About these ads

25 Responses

Subscribe to comments with RSS.

  1. da2nhikmah said, on December 12, 2012 at 6:57 pm

    Siiip…

  2. Mungiu said, on December 17, 2012 at 4:15 am

    offtopic: how can i get AuthenticationService in custom view helper because i get
    __PHP_Incomplete_Class Object
    setAuthService(new AuthenticationService);
    if ($this->getAuthService()->hasIdentity()) {
    $user = $this->getAuthService()->getIdentity();
    print_r($user);
    // $displayName = $user->getDisplayName();
    // if (null === $displayName) {
    // $displayName = $user->getUsername();
    // }
    // if (null === $displayName) {
    // $displayName = $user->getEmail();
    // $displayName = substr($displayName, 0, strpos($displayName, ‘@’));
    // }
    }
    return $displayName;
    }

    public function getAuthService() {
    return $this->authService;
    }

    public function setAuthService(AuthenticationService $authService) {
    $this->authService = $authService;
    return $this;
    }

    }

    • samsonasik said, on December 17, 2012 at 12:20 pm

      you should inject by service manager. create your view helper first.

      namespace SanCommons\View\Helper;
      
      use Zend\View\Helper\AbstractHelper;
      
      class AuthServiceHelper extends AbstractHelper
      {
          protected $authservice;
          
          public function setAuthService($service)
          {
              $this->authservice = $service;    
          }
          
          public function getAuthService()
          {
              return $this->authservice;    
          }
          
          public function __invoke($display = null)
          {
              $user =  $this->getAuthService()->getIdentity();
              if (null === $displayName) {
                  // return something..
              } else {
                  // return other something...
              }
          }    
      }
      

      Register into Service Manager.

      
      'view_helpers' => array(
          'factories' => array(
              'AuthServiceHelper' => function($sm){
                  $helper = new \SanCommons\View\Helper\AuthServiceHelper;
                  //please make sure AuthService already registered in SM
                  $request = $sm->getServiceLocator()->get('AuthService');
                  $helper->setAuthService($request);
                  return $helper;
              }
          ),
      ),
      
      

      Calling in view :

      echo $this->AuthServiceHelper('user');
      
      • Mungiu said, on December 17, 2012 at 6:59 pm

        i use \DoctrineModule\Authentication\Adapter\ObjectRepository and i think the problem is when the object are serialize and saved to session, i extended that class and change object with array to solve the problem, but i will try your way to see if it’s working

  3. jaguarnet7 said, on December 22, 2012 at 10:21 am

    Is there something like ZF1 AjaxContext which verify ‘X-Requested-With: XmlHttpRequest’ header?

  4. jaguarnet7 said, on December 24, 2012 at 8:48 pm

    Thanks, now i’ll swich viewmodel to jsonViewModel

  5. samsonasik said, on December 24, 2012 at 10:53 pm

    You’re welcome ;)

  6. Mino said, on April 9, 2013 at 7:46 pm

    Hello, I am struggling with setting xsl stylesheets to use for views. Do you have any resource where this would be explained? I tried using this blog and define a new strategy but it seems there’s much more to it. Thanks.

    • Mino said, on April 9, 2013 at 10:33 pm

      Additionaly, when trying to reproduce you png view strategy. I get following exception:

      While attempting to create imagestrategy(alias: ImageStrategy) an invalid factory was registered for this instance type.

      thrown at line where you assign $strategy in onBootsrap in Module:

      $strategy = $sm->get(‘ImageStrategy’);

      I could not resolve this problem. Thanks for any advice

      • samsonasik said, on April 10, 2013 at 1:50 am

        add the following functions to your Module.php

            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__,
                        ),
                    ),
                );
            }
        

        one more thing, don’t forget to look at the name of file and name of namespace, adapt to your created module.

  7. Mino said, on April 10, 2013 at 2:37 pm

    Thank you very much. I already had these 2 methods set up the same way as you suggested, but the problem was elsewhere, I have created the View subdir under src/ and not under src/Application so I had:

    src/
    Application/
    Controller/
    View/

    I needed to move it to get
    src/
    Application/
    Controller/
    View/

    The settings is obvious from namespace but I missed that and spent lot of time on this. Anyway, very good blog thanks and keep up.

    • Mino said, on April 10, 2013 at 2:40 pm

      white space removed, so I’m gonna try again:

      wrong dir structure (if you want the code from this blog to work):
      src/
      –Application/
      —-Controller/
      –View/

      correct:
      src/
      –Application/
      —-Controller/
      —-View/

      • samsonasik said, on April 10, 2013 at 5:33 pm
        module
            --Application
                --src
                    --Application
                        --View
                            --Strategy
                                --ImageStrategy.php
                                --ImageFactory.php
                        --Controller
                            --SkeletonController.php
                --config
                     --module.config.php
                Module.php
        
  8. […] speziell den Listener an. Da wird auch der Content-type Header verändert. Musst du also anpassen. Zend Framework 2 : Step by Step Create Custom View Strategy | Welcome to Abdul Malik Ikhsan's Blog Hilft das weiter? Gruß, […]

  9. carlos said, on February 12, 2014 at 3:13 am

    hi, i used the same concep to send emails with templates ,it works fine when is a http request but by console it cant render $serviceLocator->get(‘ViewRenderer’) , how can i solved

    • samsonasik said, on February 16, 2014 at 4:28 pm

      use Zend\View\Model\ViewModel instead. you can use $viewmodel->setTemplate() for it.

  10. Frédéric Cywier said, on February 19, 2014 at 4:47 pm

    Thank you very much !
    It works perfectly.

  11. SmasherHell said, on July 23, 2014 at 5:03 pm

    Thank you, it is the way to handle ActionControllers responses. I use it to output pdfstream.

    I wanted to implement it like ZF, with a custom ViewModel. So I added a ViewRenderer implementation and a custom ViewModel working with the strategy. Now my ActionControllers are a way cleaner.

  12. Rohit said, on August 24, 2014 at 1:10 pm

    First – I have been referring this blog quite a bit. Thanks for sharing your knowledge !

    My current goal :
    I want to have context switching for a single action so that the data format varies depending on the request header.

    My approach :
    Using acceptableViewModelSelector, my action returns a different type of data (JSONModel vs ViewModel) depending on the acceptable request header.
    I want to implement my own JSON Strategy(rather than use the inbuilt ZF2 one) using your given example.
    However, the response now always returns a json even though the request is for text rather than json.

    Any suggestion as to how I get the context switch to work ?

  13. samsonasik said, on August 25, 2014 at 4:31 am

    acceptableViewModelSelector should work, you need to check :
    1. is ‘ViewJsonStrategy’ already added into ‘view_manager’ -> ‘strategies’ config ?
    2. make sure your request using text/html header, you can check with curl.

    curl -H "Accept: application/json" http://zf2skeleton.local/sampleusageviewmodelselector
    

    or for text/html

    curl -H "Accept: text/html" http://zf2skeleton.local/sampleusageviewmodelselector
    

    3. make sure you added ‘text/html’ into your $acceptCriteria config like this :

    namespace TutorialViewModelSelector\Controller;
    
    use Zend\View\Model\JsonModel;
    use Zend\Mvc\Controller\AbstractActionController;
    
    class SampleUsageViewModelSelectorController extends AbstractActionController
    {
        protected $acceptCriteria = array(
          'Zend\View\Model\JsonModel' => array('application/json'),
          'Zend\View\Model\ViewModel' => array('text/html'),
        );
        
        public function indexAction()
        {
            $viewModel = $this->acceptableViewModelSelector($this->acceptCriteria);
            if ($viewModel instanceof JsonModel) { // will return json {"hello":"world"}
                $viewModel->setVariables(array('hello' => 'world'));
            } else {
                // you need to have view/error/requestshouldcomefromjson.phtml to check this.
                $viewModel->setTemplate('error/requestshouldcomefromjson');
            }
            
            return $viewModel;
        }
    }
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 277 other followers

%d bloggers like this: