ZF2 - Add pages to navigation in the controller - zend-framework2

In my project I have a navigation, which is created from an array in a config.php file using the default factory. I want to add subpages to the current pages in the controller.
class IndexController extends AbstractActionController {
public function newpageAction() {
$navigation = $this->getServiceLocator()->get('navigation');
$currentPage = $navigation->findById('index');
$options = array(
'id' => 'newpage',
'label' => 'New Page',
'route' => 'my-route',
'controller' => 'index',
'action' => 'newpage',
'active' => true,
);
$newpage = new \Zend\Navigation\Page\Mvc($options);
$currentPage->addPage($newpage);
}
}
The page is added successfully but then I try to create the url for the page in the breadcrumbs view using the getHref() method of the page:
<?php foreach($this->pages as $page) {?>
<li>
<?php echo $page->getLabel();?>
</li>
<?php }?>
But I get the following error for the newly added pages:
Additional information:
Zend\Navigation\Exception\DomainException
File:
\vendor\zendframework\zendframework\library\Zend\Navigation\Page\Mvc.php:198
Message:
Zend\Navigation\Page\Mvc::getHref cannot execute as no Zend\Mvc\Router\RouteStackInterface instance is composed
I guess the problem is in the way I create and add the pages to the navigation. Is there another way to do that or how I fix this error?
I want to add the pages after the 3th level in the controller instead of in the config file because there are params in the urls of the pages and the labels are dynamic.
Any suggestions for accomplishing this task in any other way are welcome.

You could add the default router.
\Zend\Navigation\Page\Mvc::setDefaultRouter ($this->getServiceLocator ()->get ('router'));

The error is due to the MVC page having unmet dependencies (the router). It is the factory's job to inject these components (depending on a URI or MVC type).
To make sure each MVC page has router injected create a new factory that in turn uses another already provided factory Zend\Navigation\Service\ConstructedNavigationFactory to create your own navigation container and return it's pages. In your example this will be just one page.
EDIT
If you have to add the navigation pages in the controller, where you do not know the page config prior to the newpageAction(); You could extend the class to allow config to be set within the controller.
For example
public function MyCustomNavFactory extends ConstructedNavigationFactory
{
// make the config optional
public function __construct($config = array())
{
$this->config = $config;
}
// Allow config to be set outside the class
public function setConfig($config)
{
$this->config = $config;
}
}
Module.php
// Module
public function getServiceConfig() {
return array(
'invokables' => array(
// Create the factory as an invokable (as there are no __construct args)
'MyCustomNavFactory' => 'App\Navigation\Service\MyCustomNavFactory'
),
);
}
The controller call would then just be simply just use
// Controller
public function newpageAction()
{
$serviceManager = $this->getServiceLocator();
$navigation = $serviceManager->get('MyCustomNavFactory');
$options = array(
'id' => 'newpage',
'label' => 'New Page',
'route' => 'my-route',
'controller' => 'index',
'action' => 'newpage',
'active' => true,
);
$navigation->setConfig($options);
$pages = $navigation->getPages($serviceManager);
}

The answer of #AlexP is correct.
But there error into Controller Action As when he call custom factory using ServiceLocator will get Object of type AbstractContainer Object Because ServiceLocator will call createService method into your custom factory (MyCustomNavFactory) which extends AbstractNavigationFactory So the next line will call setConfig method into AbstractContainer Object not into your custom factory (MyCustomNavFactory).
The correct Way to Set Breadcrumb configuration from Controller Action is:
// Controller
public function newpageAction()
{
$serviceManager = $this->getServiceLocator();
$navigationFactory = new MyCustomNavFactory();
$options = array(
'id' => 'newpage',
'label' => 'New Page',
'route' => 'my-route',
'controller' => 'index',
'action' => 'newpage',
'active' => true,
);
$navigationFactory->setConfig($options);
$pages = $navigationFactory->getPages($serviceManager);
}
OR
Remove setConfig method form custom factory and set configuration using it's Constructor
// custom Factory
public function MyCustomNavFactory extends ConstructedNavigationFactory
{
// make the config optional
public function __construct($config = array())
{
parent::__construct($config);
}
}
Then Controller will be:
// Controller
public function newpageAction()
{
$serviceManager = $this->getServiceLocator();
$options = array(
array(
'id' => 'newpage',
'label' => 'New Page',
'route' => 'my-route',
'controller' => 'index',
'action' => 'newpage',
'active' => true,
)
);
$navigationFactory = new MyCustomNavFactory($options);
$pages = $navigationFactory->getPages($serviceManager);
}

Related

How to call on view on dyanmic actions in zf2

Hello friends I am new in zf2. I stuck at one place. In my project I want to to call one view on many action.
My url is "baseurl/g/any-thing-from-from-database"
I want to call a view on "any-thing-from-from-database" action from another module or same.
My G module have this code on module.config.php
return array(
'controllers' => array(
'invokables' => array(
'G\Controller\G' => 'G\Controller\GController',
),
),
'router' => array(
'routes' => array(
'g' => array(
'type' => 'segment',
'options' => array(
'route' => '/g[/:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'G\Controller\G',
'action' => 'g',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'g' => __DIR__ . '/../view',
),
),
);
on GController.php
namespace G\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Stdlib\ResponseInterface as Response;
use Zend\View\Renderer\PhpRenderer;
use Students\Form\StudentsForm;
use Students\Model\Students;
class GController extends AbstractActionController
{
public function dispatch(Request $request, Response $response = null)
{
$controller = $this->params('controller');
$nicname = $this->params('action');
if($nicname !== false){
$hosts = $this->getServiceLocator()->get('Manager\Model\HostsTable');
if(($data = $hosts->findByNicname($nicname)) !== null){
$captchaService = $this->getServiceLocator()->get('SanCaptcha');
$form = new StudentsForm($captchaService);
return array('from'=>$form);
}
}
return $this->redirect()->toRoute('home',array('controller'=>'application','action'=>'index'));
}
public function gAction()
{
return new ViewModel();
}
}
To get different actions from url I have used dispatch function that is working correctly. When i get this action from database I want to show a form with some content from different module named Students or G. But this code only showing header and footer and nothing else without any error.Please help me out.Thanks in advance.
I think overwriting dispatch method cannot be very good.
I'm sure you don't see anything, because you don't ever render a ViewModel, so no data are there. This is because you disabled default dispatch behaviour and overwrite the action argument in your route. So if you open http://my.website/g/foobar then foobarAction() would be called - if your dispatch will work correctly.
So what you could do is simple rename your action param to (for example) foo, take your logic to gAction() and do what ever you have to do with $this->param('foo').

ZF2 An Invalid Factory Was Registered

I've the following classes and my module config in ZF2 application and it is giving the below error:
While attempting to create applicationformuserform(alias: Application\Form
\UserForm) an invalid factory was registered for this instance type.
UserFormFactory.php
<?php
namespace Application\Factory\Form;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\Form\UserForm;
class UserFormFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
$services = $serviceLocator->getServiceLocator();
$entityManager = $services->get('Doctrine\ORM\EntityManager');
$form = new UserForm($entityManager);
return $form;
}
}
?>
UserForm.php
<?php
namespace Application\Form;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;
use Doctrine\ORM\EntityManager;
class UserForm extends Form implements InputFilterProviderInterface {
protected $entityManager;
public function __construct(EntityManager $entityManager) {
parent::__construct();
$this->entityManager = $entityManager;
}
public function init() {
$this->add(array(
'name' => 'username',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => 'User Name',
),
));
$this->add(array(
'name' => 'first_name',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => 'First Name',
),
));
$this->add(array(
'name' => 'last_name',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => 'Last Name',
),
));
$this->add(array(
'name' => 'role_id',
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'options' => array(
'object_manager' => $this->entityManager,
'target_class' => 'Application\Entity\Role',
'property' => 'id',
'is_method' => true,
'find_method' => array(
'name' => 'getRoles',
),
'label' => 'User Role',
),
));
}
public function getInputFilterSpecification() {
return array(); // filter and validation here
}
}
?>
Module.config.php
'form_elements' => array(
'factories' => array(
'Application\Form\UserForm' => 'Application\Factory\Form\UserFormFactory',
),
),
And I'm using this form factory in another controller factory
UserControllerFactory.php
<?php
namespace Member\Factory\Controller;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Member\Controller\UserController;
use Application\Form\UserForm;
class UserControllerFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
$services = $serviceLocator->getServiceLocator();
$userForm = $services->get('FormElementManager')->get('Application\Form\UserForm');
$controller = new UserController($userForm);
return $controller;
}
}
?>
Could anybody tell me that what may be the issue?
Your Factory is not being found.
Check if you using PSR-4 or PSR-0 in your controller among the other answers
Briefly
did you name the Factory correctly (no misspellings)?
Is your composer.json updated with PSR-0, or PSR-4 namespaces for your modules?
did you run composer dump-autoload?
Does your autoload_classmap.php contain an outdated entry & confuses autoloader?
Check your folder structure and names
make sure your Factory implements FactoryInterface
Ask yourself "Why is my Factory class not being found when I have placed it right there" where it obviously without a doubt must be found? That will help you guide your way into finding out what's wrong.
I got my answer at my own after looking at the code again and again. Actually my Factory and Form folders were outside of src folder that's why Zend could not found all the classes of both folders.
I moved both Factory and Form folder in src and now it's working fine.
I had a similar problem. I had made some changes to my factory classes (refactoring + minor class name changes). Turns out that because I was using the Classmap Autoloader ... and forgot to re-run php vendor/bin/classmap_generator.php in the module structure ... the newly renamed classes were not found. Too bad a "class not found" error wasn't generated.

ZF2 get url param and redirect on dispatch_error event

I have the code to change the language of site. I would like to extend this functional. I want to make sure that the language parameter in the url is correct when I get the 404 page (or dispatch_error event).
My route example
'about' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:lang/]about',
'constraints' => array(
'lang' => '[a-zA-Z]{2}?',
),
'defaults' => array(
'controller' => 'Application\Controller\Index',
'action' => 'about',
'lang' => 'en',
),
),
),
If url param isn't correct (example.com/e/about or exampleDotcom//about), then makes redirect to the specific page (for example, example.com/why_did_it_happen). To make this, I create a function checkRedirect and attach it to EVENT_DISPATCH_ERROR . But how to get the LANG parameter from the url and then make a redirect, I don't know. I tried to do this many times, but could not. I've got - Call to a member function getParam () on a non-object. What code would I append to the checkRedirect function to get the LANG parameter from the url and then make a redirect in this function?
My code in Module.php
class Module implements
AutoloaderProviderInterface,
ConfigProviderInterface,
ViewHelperProviderInterface {
public function onBootstrap(MvcEvent $e) {
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$eventManager->attach(MvcEvent::EVENT_ROUTE, array($this, 'initLocale'), -100);
$eventManager->attach(MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'checkRedirect'), -101);
$eventManager->attach(MvcEvent::EVENT_DISPATCH, array($this, 'preDispatch'), 100);
}
public function initLocale(MvcEvent $e) {
$translator = $e->getApplication()->getServiceManager()->get('translator');
$config = $e->getApplication()->getServiceManager()->get('Config');
$shotLang = $e->getRouteMatch()->getParam('lang'); //or $e->getApplication()->getMvcEvent()->getRouteMatch();
if (isset($config['languages'][$shotLang])) {
$translator->setLocale($config['languages'][$shotLang]['locale']);
} else {
$lang = array_shift($config['languages']);
$translator->setLocale($lang['locale']);
}
}
public function checkRedirect(MvcEvent $e) {
//code here
}
$e->getRouteMatch()->getParam('NAME')
This does work, but 'NAME', but be the name given in the routes.
'route' => '/[:lang/]about',
However, the above route does not match the route *example.com/why_did_it_happen*
Try changing your route to
'route' => '[/:lang]/about',
And you could always default, if a lang is not supplied, i.e.
$e->getRouteMatch()->getParam('lang', 'en');

zend 2: Trying to use Zend\Navigation in my view helper

I'm trying to create a menu bar from a template in my view helper with Zend\Navigation.
I'm getting a little closer and edited this thread with code I have now.
Here is the view helper:
<?php
namespace Helpdesk\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Navbar extends AbstractHelper implements ServiceLocatorAwareInterface {
public function setServiceLocator(ServiceLocatorInterface $serviceLocator) {
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator() {
return $this->serviceLocator;
}
public function __invoke() {
$partial = array('helpdesk/helpdesk/subNavTest.phtml','default');
$navigation = $this->getServiceLocator()->get('navigation');
$navigation->menu()->setPartial($partial);
return $navigation->menu()->render();
}
}
I configured the navigation in module.config.php like so:
'view_helpers' => array(
'invokables' => array(
'navbar' => 'Helpdesk\View\Helper\Navbar',
),
),
'navigation' => array(
'default' => array(
array(
'label' => 'One',
'route' => 'link',
),
array(
'label' => 'Two',
'route' => 'link',
),
array(
'label' => 'Three',
'route' => 'link',
), ...
But when I display it in my view like this <?php echo $this->navbar(); ?> it just displays the partial template without the navigation config from module.config.php.
If I do the following right in my view it displays fine with the config that I set:
<?php $partial = array('helpdesk/helpdesk/subNavTest.phtml','default') ?>
<?php $this->navigation('navigation')->menu()->setPartial($partial) ?>
<?php echo $this->navigation('navigation')->menu()->render() ?>
Why isn't my view helper pulling in the navigation config?
If I do the following right in my view it displays fine with the config that I set:
Yes, that's because in your view (the code that works), you're telling the navigation helper to use a menu container called navigation at this line...
<?php $this->navigation('navigation')->menu()->setPartial($partial) ?>
^^^^^^^^^^- This is the menu container
In your navbar helper, you don't specify a menu container. If you haven't already used the navigation helper at that point it has no menu, and creates an empty one.
You have two choices, either tell the navigation helper which container to use before calling your helper
// set the menu
<$php $this->navigation('navigation'); ?>
// render helper
<?php echo $this->navbar(); ?>
or, have your helper accept a parameter in its __invoke method which it can pass to the helper
public function __invoke($container) {
$partial = array('helpdesk/helpdesk/subNavTest.phtml','default');
$navigation = $this->getServiceLocator()->get('navigation');
// tell navigation which container to use
$navigation($container)->menu()->setPartial($partial);
return $navigation->menu()->render();
}
and call it in your view as
<?php echo $this->navbar('navigation'); ?>

ZF2: Controller's Forward plugin doesn't work. How to make it work?

I need to forward the ajax request to the other Action method of current controller. I use the Forward plugin but it doesn't work. There is an example in the manual about how to use the Forward Plugin:
$foo = $this->forward()->dispatch('foo', array('action' => 'process'));
return array(
'somekey' => $somevalue,
'foo' => $foo,
);
My code:
// From Ajax on the page. I apply to the indexAction of FooController,
// I use RegEx route
xhr.open('get', '/fooindex', true);
// My Controller
namespace Foo\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
// I extend the AbstractActionController, the manual says it's important for the Forward Plugin to work
class FooController extends AbstractActionController {
// This is the action I send my request from Ajax
public function indexAction() {
// if the request if Ajax request I forward the run to the nextAction method
if ($this->getRequest()->isXmlHttpRequest()) {
// I do as manual says
$rs = $this->forward()->dispatch('FooController', array('action' => 'next'));
}
}
public function nextAction() {
// And I just want to stop here to see that the Forward Plugin works
// But control doesn't reach here
exit('nextAction');
}
}
The error I get in the Console is:
GET http://test.localhost/fooindex 500 (Internal Server Error)
If I do not use Forward everything works fine, the request comes to the indexAction just fine. Only Forward throws an error.
From the manual, about The Forward Plugin:
For the Forward plugin to work, the controller calling it must be
ServiceLocatorAware; otherwise, the plugin will be unable to retrieve
a configured and injected instance of the requested controller.
From the manual, about Available Controllers:
Implementing each of the above interfaces is a lesson in redundancy;
you won’t often want to do it. As such, we’ve developed two abstract,
base controllers you can extend to get started.
AbstractActionController implements each of the following interfaces:
Zend\Stdlib\DispatchableInterface
Zend\Mvc\InjectApplicationEventInterface
Zend\ServiceManager\ServiceLocatorAwareInterface
Zend\EventManager\EventManagerAwareInterface
So my FooController extends AbstractActionController, which implements ServiceLocatorAwareInterface, so the Forward has to work, but it doesn't. What did I miss? How to make it work?
You should remember that the dispatch plugin gets the controller to dispatch to from the service manager by name. You should therefore use the correct name and not just the classname.
Look in your configuration for the controllers.invokables array. That should contain which name of the service maps to what FQCN.
It might be you name IS FooController, then forget what I just said
You should use fully qualified name when calling the controller, so 'FooController' should be namespaced as well.
Also, you should add the controller in the list of the invokables in the module config files, for example:
return array(
'controllers' => array(
'invokables' => array(
'FooController' => 'Namespace/Controller/FooController'
...
),
)
)
try this:
class FooController extends AbstractActionController {
public function indexAction() {
return $this->forward()->dispatch('Bar\Controller\Bar',
array(
'action' => 'process',
'somekey' => $somevalue,
));
}
}
here invokable is: 'Bar\Controller\Bar' => 'Bar\Controller\Bar'
try this:
class FooController extends AbstractActionController {
public function indexAction() {
return $this->forward()->dispatch('Foo',
array(
'action' => 'process',
'somekey' => $somevalue,
));
}
}
Your module.config.php file is like this:
'controllers' => array(
'invokables' => array(
'Foo' => 'Foo\Controller\FooController', // <----- Module Controller
),
),
'router' => array(
'routes' => array(
'foo' => array(
'type' => 'segment',
'options' => array(
'route' => '/foo[/:action][/:id]', // <---- url format module/action/id
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Foo', // <--- Defined as the module controller
'action' => 'index', // <---- Default action
),
),
),
),
),

Resources