Injecting custom navigation using a View Helper through the ServiceManager - zend-framework2

I'm just starting out developing for Zend Framework 2. I'm trying to add a simple menu to my Application. The menu will eventually be loaded from a database as user-definable bookmarks, so at the moment, I am trying to instantiate a view helper I've defined, add pages programmatically in the controller, and then inject the view helper's navigation into the view model. My problem is that when I try to retrieve my view helper in the controller by using the ServiceLocator, I get a ServiceNotFoundException:
Application\View\Helper\ShortcutsMenu:
namespace Application\View\Helper;
use Zend\Navigation\Navigation;
class ShortcutsMenu extends Navigation {
public function shortcutsMenu() {
//...
}
public function __construct() {
//...
}
}
and in Module.php
public function getServiceConfig() {
return array(
'view_helper' => array(
'factories' => array(
'shortcutsmenu' => function($sm) {
$smenu = new \Application\View\Helper\ShortcutsMenu();
return $smenu;
}
),
),
);
IndexController.php:
$smenu = $this->getServiceLocator()->get('shortcutsmenu'); // throws ServiceNotFoundException
//"Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for shortcutsmenu"
$smenu->addPage(AbstractPage::factory(array(
'label' => 'Homepage',
'order' => '-1',
'uri' => '/',
)));
// ...
}
Can anyone tell me what I'm missing?
Edit:
The HTML I would like to generate in the application-wide layout would be something like:
<!-- Side tabs shortcuts -->
<ul id="shortcuts">
<li class="current">Home</li>
<li>My messages</li>
<li>Bob's calendar</li>
<li>...</li>
</ul>
probably using URI-style links rather than MVC ones.

There is no need to extend the navigation container Zend\Navigation\Navigation or extend the builtin view helpers to render menu's.
A container manages all pages in the navigation structure. The are several ways to create a container.
All the view helpers (menu, breadcrumbs) use the container as the provider for navigation data. You can eighter set a new container on the view helper using setContainer(). Alternatively you could just call the view helper in your view without a container setup and the view helper will create a new empty container for you.
If you need some alternate rendering because the default view helpers don't provide it you can create you own navigation view helper.
namespace MyNamespace\View\Helper\Navigation;
use Zend\View\Helper\Navigation\AbstractHelper;
class MyHelper extends AbstractHelper
{
}
Next register your view helper to the navigation pluginManager. I think you can do something like this (untested):
class Module
{
public function onBootstrap($e)
{
$application = $e->getApplication();
/** #var $serviceManager \Zend\ServiceManager\ServiceManager */
$serviceManager = $application->getServiceManager();
$pm = $serviceManager->get('ViewHelperManager')->get('Navigation')->getPluginManager();
$pm->setInvokableClass('myHelper', 'MyNamespace\View\Helper\Navigation\MyHelper');
}
}
Now call you custom helper in your view:
$this->navigation()->myHelper()

Related

How to use translate helper in controllers in zend framework 2

Is there any possible way to translate strings in controllers instead of view?
Right now, in my controllers, if I pass strings like :
public function indexAction() {
return array('message' => 'example message');
}
It will be translated in index.phtml
<?php print $this->translate($message);?>
It works well, but poeditor unable to find strings from controller files
Guess it would be cool if I can use something like :
public function indexAction() {
return array('message' => $view->translate('example message'));
}
in controllers
Thanks in advance for help
To use view helper in controller, you can use 'getServiceLocator'
$helper = $this->getServiceLocator()->get('ViewHelperManager')->get('helperName');
Either you can use php getText function ___('my custom message') and add "_" as sources keyword in poedit (in catalog properties) so poedit will filter strings from controller. eg:
array('message' => _('my custom message'));
And as per your code, you can use helper directly like this
$translate = $this->getServiceLocator()->get('ViewHelperManager')->get('translate');
array('message' => $translate('my custom message'));
You should not use the view's plugin manager to get to the translator helper. Grab the translator like I have explained here already.
A copy/paste of that post:
Translation is done via a Translator. The translator is an object and injected for example in a view helper, so if you call that view helper, it uses the translator to translate your strings. For this answer I assume you have configured the translator just the same as the skeleton application.
The best way is to use the factory to inject this as a dependency into your controller. The controller config:
'controllers' => array(
'factories' => array(
'my-controller' => function($sm) {
$translator = $sm->getServiceLocator()->get('translator');
$controller = new MyModule\Controller\FooController($translator);
}
)
)
And the controller itself:
namespace MyModule;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\I18n\Translator\Translator;
class FooController extends AbstractActionController
{
protected $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
}
An alternative is to pull the translator from the service manager in your action, but this is less flexible, less testable and harder to maintain:
public function fooAction()
{
$translator = $this->getServiceManager()->get('translator');
}
In both cases you can use $translator->translate('foo bar baz') to translate your strings.
I use for that purpose a simple plugin. Then in controller you can do $this->translate('example message');
class Translate extends AbstractPlugin {
private $translator;
public function __construct(PluginManager $pm) {
$this->translator = $pm->getServiceLocator()->get('Translator');
}
public function __invoke($message, $textDomain = 'default', $locale = null) {
return $this->translator->translate($message, $textDomain, $locale);
}
}

ZF2 Service Locator

I'm quite new to zf2 and I'm experimenting with it. I have a view helper and I need it to access a table object. In my controller I can run:
$this->getServiceLocator();
But ideally I would run this inside my view helper. Unfortunately, I can't seem to access it from within my view helper. I tried passing it through the constructor, configuring a factory method in module.config.php, but when I try that, Zend will no longer pass a tablegateway object into one of my model objects created from a service factory method in the module's Module.php file. This seems to be because it no longer calls the factory method, and opts to run instantiate without any parameters.
I'm not certain I understand why the view factory methods would affect a different set of factory methods with different names.
Can anyone tell me what is wrong with what I'm doing? I can provide more details, but at this point I'm unclear on what details are actually important without supplying the entire codebase.
Thanks.
Crisp does provide a valid answer to your question, but I would suggest to take it one step further. The injection of the service locator makes your view helper tightly coupled to the framework and service locator pattern and vulnerable because every piece of code inside your application can modify every service in the service locator.
There are reasons to inject your dependency directly, so you only depend on your dependencies and you're not implementing this anti-pattern anymore. Let's assume your view helper depends on MyModule\Model\MyTable, then the constructor of your view helper would just look like this:
namespace MyModule;
use MyModule\Model\MyTable;
use Zend\View\Helper\AbstractHelper;
class MyViewHelper extends AbstractHelper
{
protected $table;
public function __construct(MyTable $table)
{
$this->table = $table;
}
}
As you pointed out, you just inject your MyTable now:
namespace MyModule;
class Module
{
public function getViewHelperConfig()
{
return array(
'factories' => array(
'MyViewHelper' => function($sm) {
$sm = $sm->getServiceLocator(); // $sm was the view helper's locator
$table = $sm->get('MyModule_MyTable');
$helper = new MyModule\View\Helper\MyHelper($table);
return $helper;
}
)
);
}
}
Note that inside a view helper factory your service manager is the view helper's service manager and not the "main" one where the table is registered (see also a blog post of I wrote earlier). The $sm->getServiceLocator() solves this for you.
I'm not certain I understand why the view factory methods would affect a different set of factory methods with different names.
It's not, so there is probably a bug in your code. If above does not work, please provide some more details on your service manager configuration so I can update my answer.
One of the great advantages of above approach is you make unit testing really easy for your view helper. You can mock the table gateway and focus on the complete behaviour of your view helper.
use MyModule\View\Helper\MyHelper;
public function testHelperusesTable
{
$mock = $this->getMock('MyModule\Model\MyTable');
$helper = new MyHelper($mock);
// Test your $helper now
}
You can inject the service locator into your view helper from the view helper config in Module.php
// Application/Module.php
public function getViewHelperConfig()
{
return array(
'factories' => array(
'myViewHelper' => function ($serviceManager) {
// Get the service locator
$serviceLocator = $serviceManager->getServiceLocator();
// pass it to your helper
return new \Application\View\Helper\MyViewHelper($serviceLocator);
}
)
);
}
In your view helper
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper,
Zend\ServiceManager\ServiceLocatorInterface as ServiceLocator;
class MyViewHelper extends AbstractHelper
{
protected $serviceLocator;
public function __construct(ServiceLocator $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
}
While working in Zend Framework,we often need custom helper,that make our work easy, In zf1 accessing database model from helper was easy,but i got stuck that how to access database model for any table in Custom View Helper, but as i was needing it i get around through the problem in unprofessional way by creatina new db adapter object in the view, which was never good way, but recently i came to know through very interesting way to access the database adapter in the view helper and there i have to execute any query on any table, it may be not so Zend F2 way, but very simple and short way to solve the issue.
Here is my Model Example...
<?php
namespace Application\Model;
use Zend\Db\TableGateway\TableGateway;
class SlideImageSubTable {
protected $tableGateway;
public $adapter;
public function __construct(TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
$this->adapter = $this->tableGateway->getAdapter();
}
public function fetchAll() {
$resultSet = $this->tableGateway->select();
return $resultSet;
}
public function getSlideImageSub($id) {
$id = (int) $id;
$rowset = $this->tableGateway->select(array('id' => $id));
$row = $rowset->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}
public function getImageMenu($id) {
$id = (int) $id;
$rowset = $this->tableGateway->select(array('slide_image_id' => $id));
$rows = array_values(iterator_to_array($rowset));
if (!$rows) {
throw new \Exception("Could not find row $id");
}
return $rows;
}
public function saveSlideImageSub(SlideImageSub $slideImageSub) {
$data = array(
'slide_image_id' => $slideImageSub->slide_image_id,
'title' => $slideImageSub->title,
'description' => $slideImageSub->description
);
$id = (int) $slideImageSub->id;
if ($id == 0) {
$this->tableGateway->insert($data);
} else {
if ($this->getSlideImageSub($id)) {
$this->tableGateway->update($data, array('id' => $id));
} else {
throw new \Exception('Form id does not exist');
}
}
}
public function deleteSlideImageSub($id) {
$this->tableGateway->delete(array('id' => $id));
}
}
Just look at the 'public $adapter' public variable. And in the constructor i am going to initialize it by calling $this->tableGateway->getAdapter(); method, getAdapter() is available thorugh gateway object.
Then in my controller action view, i have to assign it to any variable and pass that variable to view page. like this..
public function equitiesAction() {
$image_id = $this->params('id');
$result = $this->getTable('SlideImageSub')->getImageMenu($image_id);
$adapter = $this->table->adapter;
$view = new ViewModel(array(
'result' => $result,
'adapter' => $adapter,
));
return $view;
}
And in the view i pass the 'adapter' object to custom view like this..
<?php echo $this->GetMenuProducts( $this->adapter); ?>
Now in custom view i can use this database adapter object and create select query on any table.
Hope this will help someone, i look around for using database access in custom view helper but the configurations methods provided was not working for me.
Thanks
$this->getView()->getHelperPluginManager()->getServiceLocator();

Zend Framework 2 - How do I change the view script in the controller plugin?

I have a problem with Zend Framework 2.
I want to have 2 view scripts for each action to display the appropriate view for PC and smartphone. I know how to change the layout:
$viewModel->setTemplate(layout_path)
but I could not find out how to change the view script.
I have following files in the index view folder, Application/view/application/index, for IndexController:
index.php (view file for PC)
index_sp.php (view file for smartphone)
How do I change the view script to index_sp.php in the controller or controller plugin?
If you want to change the actual layout view file you can do this inside your controller:
// example to change base layout for ajax requests
if($this->getRequest()->isXmlHttpRequest()) {
$this->layout('layout/ajax-layout');
}
If you want to change the view used by the current view model / action you can do this inside your controller/action:
$viewModel = new ViewModel(array(
'form' => $form
'something' => $something
));
$viewModel->setTemplate('mymodule/newview.phtml');
return $viewModel;
public function doSomethingCrazyAction() {
$view = new ViewModel(array(
'message' => 'Hello world',
));
// This is the way to change render(view)..
$view->setTemplate('modulename/controllername/phtml-file-name');
return $view;
}
Docu: http://framework.zend.com/manual/current/en/modules/zend.view.quick-start.html

How to create custom form element in Zend Framework 2?

How can I in ZF2 create custom form element with custom validator? I want to create custom category picker that uses jQuery and content of this element should be rendered from phtml script. In ZF1 it was quite easy but in ZF2 I don't know from where to start.
A form element must implement a Zend\Form\ElementInterface. A default class is the Zend\Form\Element which you can use as a base form:
<?php
namespace MyModule\Form\Element;
use Zend\Form\Element;
class Foo extends Element
{
}
CUSTOM VALIDATOR
You can let the element directly assign a custom validator. Then you must implement the Zend\InputFilter\InputProviderInterface:
<?php
namespace MyModule\Form\Element;
use Zend\Form\Element;
use Zend\InputFilter\InputProviderInterface;
use MyModule\InputFilter\Bar as BarValidator;
class Foo extends Element implements InputProviderInterface
{
protected $validator;
public function getValidator()
{
if (null === $this->validator) {
$this->validator = new BarValidator;
}
return $this->validator;
}
public function getInputSpecification()
{
return array(
'name' => $this->getName(),
'required' => true,
'validators' => array(
$this->getValidator(),
),
);
}
}
CUSTOM RENDERING
At this moment it is a bit complex how Zend Framework handles the rendering of custom form element types. Usually, it just returns plain <input type="text"> elements.
There is one option, then you have to override the Zend\Form\View\Helper\FormElement helper. It is registered as formelement and you must override this view helper in your custom module:
namespace MyModule;
class Module
{
public function getViewHelperConfig()
{
return array(
'invokables' => array(
'formelement' => 'MyModule\Form\View\Helper\FormElement',
'formfoo' => 'MyModule\Form\View\Helper\FormFoo',
),
);
}
}
Furthermore, every form element in Zend Framework 2 is rendered by a view helper. So you create a view helper for your own element, which will render the element's content.
Then you have to create your own form element helper (MyModule\Form\View\Helper\FormElement):
namespace MyModule\Form\View\Helper;
use MyModule\Form\Element;
use Zend\Form\View\Helper\FormElement as BaseFormElement;
use Zend\Form\ElementInterface;
class FormElement extends BaseFormElement
{
public function render(ElementInterface $element)
{
$renderer = $this->getView();
if (!method_exists($renderer, 'plugin')) {
// Bail early if renderer is not pluggable
return '';
}
if ($element instanceof Element\Foo) {
$helper = $renderer->plugin('form_foo');
return $helper($element);
}
return parent::render($element);
}
}
As a last step, create your view helper to render this specific form element:
namespace MyModule\Form\View\Helper;
use Zend\Form\ElementInterface;
use Zend\Form\View\Helper\AbstractHelper;
class Foo extends AbstractHelper
{
public function __invoke(ElementInterface $element)
{
// Render your element here
}
}
If you want to render a .phtml file for example for this form element, load it inside this helper:
namespace MyModule\Form\View\Helper;
use Zend\Form\ElementInterface;
use Zend\Form\View\Helper\AbstractHelper;
class Foo extends AbstractHelper
{
protected $script = 'my-module/form-element/foo';
public function render(ElementInterface $element)
{
return $this->getView()->render($this->script, array(
'element' => $element
));
}
}
It will render a my-module/form-element/foo.phtml and in this script you will have a variable $element which contains your specific form element.

How to render a different view in controller action of ZF2

How to render a different view other than default in controller action. by default it try to find the same view as action in the view folder but I would like render different view available in views folder for controler action.
We can do this ZF1 like this $this->_helper->viewRenderer('foo');
Can Anyone know, how to achieve above in Zendframework 2?
We can disabled the view using
$response = $this->getResponse();
$response->setStatusCode(200);
$response->setContent("Hello World");
return $response;
I don't know how to change/render a different view in zf2.
can be done using
public function abcAction()
{
$view = new ViewModel(array('variable'=>$value));
$view->setTemplate('module/controler/action.phtml'); // path to phtml file under view folder
return $view;
}
Thanks to akrabat for covering almost every scenario.
My solution in Zend Framewor 2 is simple. For index action i prefer to call parrent::indexAction() constructor bcs we extend Zend\Mvc\Controller\AbstractActionController . Or just return array() in indexAction. ZF will atomaticly return index.pthml whitout definig what must be returned.
return new ViewManager() is the same return array()
<?php
namespace Test\Controller;
use Zend\Mvc\Controller\AbstractActionController,
Zend\View\Model\ViewModel;
// Or if u write Restful web service then use RestfulController
// use Zend\Mvc\Controller\AbstractRestfulController
class TestController extends AbstractActionController
{
/*
* Index action
*
* #return main index.phtml
*/
public function indexAction()
{
parent::indexAction();
// or return new ViewModel();
// or much simple return array();
}
/*
* Add new comment
*
* #return addComment.phtml
*/
public function addAction()
{
$view = new ViewManager();
$view->setTemplate('test/test/addComment.phtml'); // module/Test/view/test/test/
return $view;
}
Dont forget to configure route and view_manager in module/config/module_config
'view_manager' => array(
'template_path_stack' => array(
'Test' => __DIR__ . '/../view',
),
),

Resources