Zend2 navigation - php

I would like to make navigation buttons in my view, for example index.phtml but it's not working. I did know how to do it in Zend1 but in Zend2 I have a problem. My code looks like this (file index.phtml):
$container = new \Zend\Navigation\Navigation($tableActions);
var_dump($container);
echo '<div class="table-column">';
echo $this->navigation($container)->menu();
echo '</div>';
Variable $tableAction looks like this:
public $tableActions = array(
array(
'label' => 'On/Off',
'module' => 'import',
'controller' => 'import',
'action' => 'setstatus',
'params' => array('id' => null),
),
);
I did not get any error, just whole site die on this line. var_dump returns object(Zend\Navigation\Navigation) so it's fine so far. Problem is, how to show it...

The navigation pages have dependencies which aren't being met by just creating a new container class in a view. The Mvc page needs a RouteStackInterface (Router) instance and a RouteMatch instance. Similarly Uri pages need the current Request instance.
You can see this clearly if you take a look at the Zend\Navigation\Service\AbstractNavigationFactory and its preparePages and injectComponents methods.
The view is not the right place to be instantiating menus, instead put the menu configuration spec in your module.config.php...
<?php
return array(
'navigation' => array(
'table_actions' => array(
array(
'label' => 'On/Off',
'module' => 'import',
'controller' => 'import',
'action' => 'setstatus',
'params' => array('id' => null),
),
),
),
);
Write a factory extending the AbstractNavigationFactory class and implement the getName() method which returns the name of your menu spec key (table_actions in this example)
<?php
namespace Application\Navigation\Service;
use Zend\Navigation\Service\AbstractNavigationFactory;
class TableActionsFactory extends AbstractNavigationFactory
{
/**
* #return string
*/
protected function getName()
{
return 'table_actions';
}
}
Map the factory to a service name in the service_manager spec of module.config.php ...
<?php
return array(
'navigation' => array(// as above ... )
'service_manager' => array(
'factories' => array(
'TableActionsMenu' => 'Application\Navigation\Service\TableActionsFactory',
),
),
);
Now you can call the view helper using the service name TableActionsMenu you just mapped
<div class="table-column">
<?php echo $this->navigation('TableActionsMenu')->menu(); ?>
</div>
Finally, if, as I suspect, you need to change an attribute of the page depending on the view, you can do that too, navigation containers have find* methods which can be accessed from the navigation helper and used to retrieve pages.
Here's an example looking for the page with a matching page label, then changing it before rendering (obviously not an ideal search param, but it gives you the idea)
$page = $this->navigation('TableActionsMenu')->findOneByLabel('On/Off');
$page->setLabel('Off/On');
// and then render ...
echo $this->navigation('TableActionsMenu')->menu();

Related

Alias "custom.controllers.ExampleController.php" is invalid

This is the error:
Alias "custom.controllers.ExampleController.php" is invalid. Make sure
it points to an existing PHP file and the file is readable.
My code is given below
main.php=>
return
array(
'controllerMap' => array(
'product' => array(
'class' => 'custom.controllers.Product.php',
),
),
'import' => array(
'custom.mycompany.*',
),
'components' =>
array(
'widgetHandler' => array(
//Load a component
'class' => 'custom.mycompany.mywidget.mywidget',
),
)
);
Product.php=>
<?php
class Product extends Controller
{
public function actionIndex()
{
echo "this is the default index function";
}
public function actionTest()
{
echo "This is the test function";
}
}
I am using lightspeed cms.
the notation for Yii2 / php class is not dot based but slash based
'class' => 'custom\controllers\Product.php',
(And in your code there is not the ExampleController..)
see p https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
and https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md
https://github.com/yiisoft/yii2/blob/master/docs/internals/core-code-style.md
for PHP an Yii2 coding standard suggestion

ZF2 Routes - The requested URL could not be matched by routing

I'm having trouble setting up a route for a very simple controller. I'm getting the "The requested URL could not be matched by routing." error. I've viewed similar solved questions on SO and can't pinpoint what I'm doing wrong (Ex: ZF2 - Zend Framework 2, understanding routing)
I've followed the skeleton tutorial with the albums subject and everything functioned perfectly fine. I tried duplicating the album module and then changing the name of the controller, folder, module config, etc. I figured this would be a good way to confirm I can at least replicate working code. I'm just trying to echo "123" to the page, so I tried eliminating the directories for forms, models and some of the views from the new module.
Is there some way to see what route I'm really looking for and what routes I defined? I know CI actually created a log file I was able to check. It was kind of like Apache logs but specific to framework functionality.
I'd like to post some of my code so someone could point out the mistake I am making and possibly explain why it is wrong. I tried paying close attention to case since different variations of the word album are used throughout the tutorial and I'm not 100% sure which ones are supposed to match up with what just yet. I'm trying to make it work for http://www.example.com/productbriefs.
Folder Structure
module.config.php:
return array(
'controllers' => array(
'invokables' => array(
'Productbriefs\Controller\Productbriefs' => 'Productbriefs\Controller\ProductbriefsController',
),
),
// The following section is new and should be added to your file
'router' => array(
'routes' => array(
'productbriefs' => array(
'type' => 'Literal',
'options' => array(
'route' => '/productbriefs',
'defaults' => array(
'controller' => 'Productbriefs\Controller\Productbriefs',
'action' => 'index',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'productbriefs' => __DIR__ . '/../view',
),
),
);
ProductbriefsController.php
namespace Productbriefs\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class ProductbriefsController extends AbstractActionController
{
public function indexAction()
{
echo "123";
}
}
Module.php
namespace Productbriefs;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
class Module implements AutoloaderProviderInterface, ConfigProviderInterface
{
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
// Add this method:
public function getServiceConfig()
{
return array(
'factories' => array(),
);
}
}
As per my comment, you need to add Productbriefs to the module array in application.config.php or the module (including its configuration) will not be loaded.
To answer your second question, the controller manager needs to know how to load the controller classes your application uses. An 'invokable' is a class that can be instantiated without needing any arguments passed to it, so by adding controllers to that array you're telling the controller manager that it can instantiate that class simply by doing $controller = new Productbriefs\Controller\ProductbriefsController(). The key for the array is an alias, yes. This can be anything, although the ZF convention is to use the fully qualified name of the class but omit the 'Controller' suffix from the end. When you refer to controllers in your routing config you use these aliases.

zendframework2 : How to pass a parameter from one controller to another

There was a similar thread but it didn't address this exact problem so I thought I would create a new thread to make this clear.
Code/problem explained:
This is my SystemsController.
Here I have viewsystemAction(). This is loaded, and from the view a script is called to load ajaxviewsystemAction().
In the viewsystemAction(), I use the params function to get the "id" parameter from my route. For example systems/viewsystems/222 ( <-- 222 is id. ). I echo this out just to show that it is correct, and it gives the correct id for the page.
The ajaxviewsystemsAction is kind of where the problem is. This is because the route of ajaxviewsystemsAction id is not the same as viewsystemsAction. If I echo the id from the params here, it displays as 0.
Although if i go to this page in the browser for example ajaxviewsystemsAction/222 then it is working perfectly. However, nothing is rendered here except the ajax table, as remember it is called in viewsystemAction from a script.
What i need to do, is somehow pass the id route value from viewsystemAction into ajaxviewsystemAction, to both use this same route id.
This is so when someone clicks on the link, it loads viewsystems/222, the correct id is executed in both viewsystemsAction and ajaxviewsystemsAction.
Is this possible? If not how could i make something similar like this. I am using zftable which integrates ajax. I need to pass this id parameter through into a query.
private function getSourceViewAllSystems($id)
{
return $this->getSystemsTable()->fetchViewAllSystems($id); //paramater to model which executes sql ->where system = $id
}
public function viewsystemAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
echo $id; //i see the correct id for example 220 from the route in the browser
}
public function ajaxviewsystemAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
echo $id; //to see the id of the route with the ajax page
//displays 0 and not the route id from the viewsystemAction
$table = new TableExample\Advance();
$table->setAdapter($this->getDbAdapter())
->setSource($this->getSourceViewAllSystems($id))
->setParamAdapter($this->getRequest()->getPost())
;
return $this->htmlResponse($table->render('custom' , 'custom-b2'));
}
--- update ---
<?php
return array(
'controllers' => array(
'invokables' => array(
'Systems\Controller\Systems' => 'Systems\Controller\SystemsController',
),
),
// The following section is new and should be added to your file
'router' => array(
'routes' => array(
'systems' => array(
'type' => 'segment',
'options' => array(
'route' => '/systems[/][:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Systems\Controller\Systems',
'action' => 'index',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'systems' => __DIR__ . '/../view',
),
),
);

How to inject the Doctrine ObjectManager into form element

I'm working on my custom User module, which basically uses ZfcUser as the foundation. Since every application is different and requires different meta information about users I want this to be easily configurable using a config array.
In my module's global config file I define the custom form fields and in the Module's onBootstrap I extend the ZfcUser registration form using the init event of ZfcUser\Form\Register. So in short I want to do something like this:
$sharedEvents->attach('ZfcUser\Form\Register',
'init',
function($e) use ($sm)
{
/* #var $form \ZfcUser\Form\Register */
$form = $e->getTarget();
// Get relevant config
$config = $sm->get('config');
if ( array_key_exists('redev_user', $config) && is_array($config['redev_user']) )
{
if ( array_key_exists('custom_fields', $config['redev_user']) && is_array($config['redev_user']['custom_fields']) )
{
foreach ($config['redev_user']['custom_fields'] as $curCustomField)
{
$form->add($curCustomField);
}
}
}
[...]
In my config file I then define the custom form fields like this:
<?php
return array(
'redev_user' => array(
'custom_fields' => array(
// Custom fields which will be added to the registration form
array(
'name' => 'firstname',
'type' => 'text',
'options' => array(
'label' => 'First name',
),
),
I do the same thing for the validators; they are being defined in the config file and attached to the form elements in the onBootstrap.
This all works nice and dandy, except when I need a Doctrine form element. In my specific case I would like to use a DoctrineModule\Form\Element\ObjectSelect for the country selectbox. In my config this would look like this:
array(
'name' => 'country',
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'options' => array(
'label' => 'Country',
//'object_manager' => $sm->get('Doctrine\ORM\EntityManager'),
'target_class' => 'RedevUser\Entity\Country',
'property' => 'countryname',
'is_method' => false,
'find_method' => array(
'name' => 'findBy',
'params' => array(
'criteria' => array(),
'orderBy' => array('countryname' => 'ASC'),
),
),
),
),
Note the commented out line for the object_manager. The ObjectSelect element obviously needs the ObjectManager. The question is how do I inject the ObjectManager while rendering the form based on the config.
I was thinking to render the form element myself and then check if it's an instance of some interface or base class of DoctrineModule\Form\Element. However it turns out there is no such base class or interface. The only thing those elements have in common is that they have a getProxy. Right now my code in onBootstrap looks like this:
foreach ($config['redev_user']['custom_fields'] as $curCustomField)
{
$formElemFactory = $form->getFormFactory();
$elem = $formElemFactory->createElement($curCustomField);
if ($elem instanceof \DoctrineModule\Form\Element\ObjectSelect)
{
// Inject ObjectManager
$elem->getProxy()->setObjectmanager($sm->get('Doctrine\ORM\EntityManager'));
}
$form->add($elem);
}
But I don't really want to check for the different Doctrine form element types. Also it seems a bit dirty to do it like this. Any opinions or ideas of how to do this better/cleaner?

CakePHP: Can I Promote active router with Router::promote or some other way?

I've got two or more routes that will be going to the same controller and action. This is fine until I want to use a helper such as the form helper or pagination on the page.
What happens is that the current url changes to whatever is declared first in my routes.php file.
I see there is a way to promote a router with Router::promote but I'm not sure if I can do it based on the current url or router being used or if there's a bett way to do this.
Here's an example of what my router.php looks like:
Router::connect('/cars-for-sale/results/*', array('controller' => 'listings', 'action' => 'results'));
Router::connect('/new-cars/results/*', array('controller' => 'listings', 'action' => 'results'));
Router::connect('/used-cars/results/*', array('controller' => 'listings', 'action' => 'results'));
Let's say for example that I'm at the url domain.com/used-cars/results/ and I'm using the form helper or pagination helper, the url that is being put in the action or href is domain.com/cars-for-sale/results/.
Any help or info would be appreciated.
Routes should be unique and identifiable!
The problem with these Routes is that, basically, you created duplicate URLs not only does this cause problems with CakePHP picking the right route, Google doesn't like that as well; duplicated content will have a negative effect on your SEO ranking!
In order to pick the right URL (Route), CakePHP should be able to do so, based on its parameters; your current Routes do not offer any way to distinguish them.
And neither does your application!
All these URLs will present the same data;
/cars-for-sale/results/
/new-cars/results/
/used-cars/results/
Solution 1 - separate actions
If your application is limited to these three categories, the easiest solution is to create three actions, one per category;
Controller:
class ListingsController extends AppController
{
const CATEGORY_NEW = 1;
const CATEGORY_USED = 2;
const CATEGORY_FOR_SALE = 3;
public $uses = array('Car');
public function forSaleCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_FOR_SALE)));
}
public function newCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_NEW)));
}
public function usedCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_USED)));
}
}
Routes.php
Router::connect(
'/cars-for-sale/results/*',
array('controller' => 'listings', 'action' => 'forSaleCars')
);
Router::connect(
'/new-cars/results/*',
array('controller' => 'listings', 'action' => 'newCars')
);
Router::connect(
'/used-cars/results/*',
array('controller' => 'listings', 'action' => 'usedCars')
);
Solution 2 - Pass the 'category' as parameter
If the list of URLs to be used for the 'listings' will not be fixed and will expand, it may be better to pass the 'filter' as a parameter and include that in your routes;
routes.php
Router::connect(
'/:category/results/*',
array(
'controller' => 'listings',
'action' => 'results',
),
array(
// category: lowercase alphanumeric and dashes, but NO leading/trailing dash
'category' => '[a-z0-9]{1}([a-z0-9\-]{2,}[a-z0-9]{1})?',
// Mark category as 'persistent' so that the Html/PaginatorHelper
// will automatically use the current category to generate links
'persist' => array('category'),
// pass the category as parameter for the 'results' action
'pass' => array('category'),
)
);
Read about the Router API
In your controller:
class ListingsController extends AppController
{
public $uses = array('Car');
/**
* Shows results for the specified category
*
* #param string $category
*
* #throws NotFoundException
*/
public function results($category = null)
{
$categoryId = $this->Car->Category->field('id', array('name' => $category));
if (!$categoryId) {
throw new NotFoundException(__('Unknown category'));
}
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => $categoryId)));
}
}
And, to create a link to a certain category;
$this->Html->link('New Cars',
array(
'controller' => 'listings',
'action' => 'results',
'category' => 'new-cars'
)
);

Categories