action parameters routing not working in Zend framework routes.ini - zend-framework

I'm trying to set up a route in Zend Framework (version 1.11.11) in a routes.ini file, which would allow be to match the following url:
my.domain.com/shop/add/123
to the ShopController and addAction. However, for some reason the parameter (the number at the end) is not being recognized by my action. The PHP error I'm getting is
Warning: Missing argument 1 for ShopController::addAction(), called in...
I know I could set this up using PHP code in the bootstrap, but I want to understand how to do this type of setup in a .ini file and I'm having a hard time finding any resources that explain this. I should also point out that I'm using modules in my project. What I've come up with using various snippets found here and there online is the following:
application/config/routes.ini:
[routes]
routes.shop.route = "shop/add/:productid/*"
routes.shop.defaults.controller = shop
routes.shop.defaults.action = add
routes.shop.defaults.productid = 0
routes.shop.reqs.productid = \d+
Bootstrap.php:
...
protected function _initRoutes()
{
$config = new Zend_Config_Ini(APPLICATION_PATH . '/configs/routes.ini', 'routes');
$router = Zend_Controller_Front::getInstance()->getRouter();
$router->addConfig( $config, 'routes' );
}
...
ShopController.php
<?php
class ShopController extends Egil_Controllers_BaseController
{
public function indexAction()
{
// action body
}
public function addAction($id)
{
echo "the id: ".$id;
}
}
Any suggestions as to why this is not working? I have a feeling I'm missing something fundamental about routing in Zend through .ini files.

Apparently I'm more rusty in Zend than I thought. A few minutes after posting I realized I'm trying to access the parameter the wrong way in my controller. It should not be a parameter to addAction, instead I should access it through the request object inside the function:
correct addAction in ShopController:
public function addAction()
{
$id = $this->_request->getParam('productid');
echo "the id: ".$id;
}
I also realized I can simplify my route setup quite a bit in this case:
[routes]
routes.shop.route = "shop/:action/:productid"
routes.shop.defaults.controller = shop
routes.shop.defaults.action = index

Related

Multiple Slim routes with the same signature

We are looking at using Slim 3 as the framework for our API. I have searched SO and the Slim docs, but cannot find an answer to the issue. If we have different route files (e.g. v1, v2, etc.) and if two routes have the same signature, an error is thrown. Is there any way to cascade the routes so that the last loaded route for a particular signature is used?
For example, say v1.php has a route for GET ("/test") and v2.php also contains this route, can we use the latest version? Even simpler would be if a file of routes contains two routes with the same signature, is there a way of the latter method being used (and no error being thrown)?
A similar question is asked here but this uses hooks (which have been removed from Slim 3 as per here)
I looked at the Slim code and I didn't find a simple way of allowing duplicated routes (preventing the exception).
The new Slim uses FastRoute as dependency. It calls FastRoute\simpleDispatcher and doesn't offer any configuration possiblity. Even if it did allow some configuration, FastRoute doesn't have any built-in option to allow duplicated routes. A custom implementation of a DataGenerator would be needed.
But following the instructions above, we can get a custom DataGenerator by passing to Slim App a custom Router which instantiates some FastRoute::Dispatcher implementation which then uses the custom DataGenerator.
First the CustomDataGenerator (let's go the easy way and do some copy and pasting from \FastRoute\RegexBasedAbstract and \FastRoute\GroupCountBased)
<?php
class CustomDataGenerator implements \FastRoute\DataGenerator {
/*
* 1. Copy over everything from the RegexBasedAbstract
* 2. Replace abstract methods with implementations from GroupCountBased
* 3. change the addStaticRoute and addVariableRoute
* to the following implementations
*/
private function addStaticRoute($httpMethod, $routeData, $handler) {
$routeStr = $routeData[0];
if (isset($this->methodToRegexToRoutesMap[$httpMethod])) {
foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) {
if ($route->matches($routeStr)) {
throw new BadRouteException(sprintf(
'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"',
$routeStr, $route->regex, $httpMethod
));
}
}
}
if (isset($this->staticRoutes[$httpMethod][$routeStr])) {
unset($this->staticRoutes[$httpMethod][$routeStr]);
}
$this->staticRoutes[$httpMethod][$routeStr] = $handler;
}
private function addVariableRoute($httpMethod, $routeData, $handler) {
list($regex, $variables) = $this->buildRegexForRoute($routeData);
if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) {
unset($this->methodToRegexToRoutesMap[$httpMethod][$regex]);
}
$this->methodToRegexToRoutesMap[$httpMethod][$regex] = new \FastRoute\Route(
$httpMethod, $handler, $regex, $variables
);
}
}
Then the custom Router
<?php
class CustomRouter extends \Slim\Router {
protected function createDispatcher() {
return $this->dispatcher ?: \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) {
foreach ($this->getRoutes() as $route) {
$r->addRoute($route->getMethods(), $route->getPattern(), $route->getIdentifier());
}
}, [
'routeParser' => $this->routeParser,
'dataGenerator' => new CustomDataGenerator()
]);
}
}
and finally instantiate the Slim app with the custom router
<?php
$app = new \Slim\App(array(
'router' => new CustomRouter()
));
The code above, if a duplicated route is detected, removes the previous route and stores the new one.
I hope I didn't miss any simpler way of achieving this result.

Zend Frameworks Quickstart find() methods Action and View

I am attempting to extend the Zend Framework Quickstart tutorial by trying to make an individual view for each guestbook entry, but I am missing something and keep getting errors like:
Trying to get property of non-object in C:\wamp\www\quickstart.local\application\views\scripts\guestbook\display.phtml
I get this when trying the following for my displayAction and my display.phtml:
//view
<p><?php echo $this->escape($this->entry->id); ?></strong> <a><?php echo $this->escape($this->entry->comment); ?></a><br>
<?php echo $this->escape($this->entry->email); ?></p>
//action
public function displayAction()
{
$id = $this->getRequest()->getParams('id');
$entry = new Application_Model_GuestbookMapper();
$this->view->entry = $entry->find($id);
}
And the find() function in the mapper is as is from the tutorial.
I have look all over the web and have only found tutorials that omit the quickstart guide's structure altogether. While they are all solid in their own right, I would like to find a solution to this. What am I doing wrong?
I am about mid-level with php and a beginner with zend framework. Please keep that in mind when responding.
If you're following the quickstart verboten, you'll want something like this
public function displayAction()
{
$id = $this->getRequest()->getParam('id');
$model = new Application_Model_Guestbook;
$mapper = new Application_Model_GuestbookMapper;
$mapper->find($id, $model);
if (null === $model->getId()) {
throw new Zend_Controller_Action_Exception(
sprintf('Guestbook entry %d not found', $id), 404);
}
$this->view->entry = $model;
}
in order to use GuestbookMapper with the Find() method you need to supply two items of information to the method, the id and an instance of Guestbook. The instance of guestbook is required because it has all the getters and setters mapper uses to generate the returned data.
//action updated
public function displayAction()
{
$id = $this->getRequest()->getParams('id');
$guestbook = new Application_Model_GuestBook();
$entry = new Application_Model_GuestbookMapper();
$this->view->entry = $entry->find($id, $guestbook);
}
for a detailed explaination of how this works check out chapter 9 of Survive the Deepend

Zend Framework: How to map fully dynamic route

The title of this question was hard to come up with, it might not be the best, anyway.
I have a site with regions, categories and suppliers, the obvious solution is to use the default route of
"/:module/:controller/:action"
So that my URLs will look something like this
"/region/midlands/category/fashion"
"/region/midlands/supplier/ted-baker"
What I want to achieve is a URL format like this however, this would need to involve a database query to check for the existence of midlands, fashion and ted-baker
"/midlands/fashion"
"/midlands/ted-baker"
My original solution was to use something like this
"/region/midlands/fashion"
With a route defined as
routes.category.route = "/region/:region/:category"
routes.category.defaults.controller = category
routes.category.defaults.action = index
routes.category.defaults.module = default
routes.category.defaults.category = false
routes.category.defaults.region = false
routes.supplier.route = "/supplier/:supplier"
routes.supplier.defaults.controller = supplier
routes.supplier.defaults.action = index
routes.supplier.defaults.module = default
routes.supplier.defaults.supplier = false
But that means prefixing everything with region or supplier. I almost need to hijack the request completely with a plug in?
What is the best way of achieving this?
Thanks for any help.
Edit.
#St.Woland, the problem is that I want this route
/:region/:supplier
To work with this URL
/midlands/ted-baker
But that route effectively overrides the default router
The best way is to add a method into your Bootstrap class like this:
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initMyRoutes()
{
// First, initialize Database resource
$this->bootstrap('db');
// Second, initialize Router resource
$this->bootstrap('router');
// Finally, instantiate your database table with routes
$m_routes = new Model_Routes() ;
// Now get the Router
$router = $this->getResource('router');
// ... and add all routes from the database
foreach( $m_routes->fetchAll() as $route ) {
$router->addRoute( $route->name, new Zend_Controller_Router_Route( $route->path, $route->toArray() ) ) ;
}
}
}
Then in your application.ini:
[production]
bootstrap.path = APPLICATION_PATH/Bootstrap.php
bootstrap.class = Bootstrap
This will initialize the router with routes from the database.
You should keep in mind, that queing the database upon every request is not efficient, so make sure to use cache.
I have used the ErrorController to do this in the end. I catch the Exception where there is controller or no route and then act accordingly.
There are calls made to a few database tables for the specific route which cannot be found rather than fetching all as in St.Woland's solution. The results are cached with tags which helps a great deal, this removes all database queries for finding routes
public function errorAction()
{
$errors = $this->_getParam('error_handler');
switch ($errors->type)
{
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
//Code for locating routes in Db goes here
}
}

Can't load Action Helpers in Zend-framework application

I'm trying to create an Action Helper, but I'm havin't a hard time loading it and I'm getting this error:
Message: Action Helper by name Usersession not found
In my controllers action method, where i'm trying to call this helper, I have this:
Zend_Controller_Action_HelperBroker::addPath('/helpers/');
Zend_Controller_Action_HelperBroker::addPrefix('Helper');
$userSession = $this->_helper->getHelper('Usersession');
$this->view->session = $userSession->eendersWat();
I'd actually prefere to load my helpers from bootstrap.php, but wasn't able to figure that out neither.
My helpers are located in application/controller/helpers. My helper filename is Usersession.php and the class is called Helper_Usersession
Any ideas why this isn't working?
I use something like the following in Bootstrap:
protected function _initHelperPath()
{
Zend_Controller_Action_HelperBroker::addPath(
APPLICATION_PATH . '/controllers/helpers',
'Application_Controller_Action_Helper_');
}
The helper class is then named 'Application_Controller_Action_Helper_Usersession' and the file is located in application/controllers/helpers/Usersession.php
Of course, this presumes you are using Application_ as your application namespace. In your case, it appears that you are using an empty application namespace and none of my wordy Controller_Action_ infix, so your's would be something like:
protected function _initHelperPath()
{
Zend_Controller_Action_HelperBroker::addPath(
APPLICATION_PATH . '/controllers/helpers',
'Helper_');
}

Is there a way to redirect the browser from the bootstrap in Zend Framework?

I need to redirect according to some conditions in the bootstrap file.
It is done AFTER the front controller and routes are defined.
How do I do that?
(I know I can simply use header('Location: ....) The point is I need to use the Router to build the URL.
more than year later, i'm programming in ZF and I got this solution for your problem.
Here is my function in bootstrap that determines where the user is logged on.
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected $_front;
(...)
protected function _initViewController()
{
(...)
$this->bootstrap('FrontController');
$this->_front = $this->getResource('FrontController');
(...)
}
protected function _initLogado()
{
$router = $this->_front->getRouter();
$req = new Zend_Controller_Request_Http();
$router->route($req);
$module = $req->getModuleName();
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
$this->_view->logado = (array) $auth->getIdentity();
} else {
$this->_view->logado = NULL;
if ($module == 'admin') {
$response = new Zend_Controller_Response_Http();
$response->setRedirect('/');
$this->_front->setResponse($response);
}
}
}
}
Redirection should really not be in the bootstrap file... That will be one horrible night of debugging for the coder that ends up stuck with your code in a few years.
Use either a Front Controller Plugin, Action Controller Plugin, or do it in your Action Controller. Ultimately such a redirect should be avoided altogether...
The best way is probably a Controller Plugin
You can add a routeShutdown() hook that is called after routing has occured, but before the action method your controller is called. In this plugin you can then check the request data or maybe look for permissions in an ACL, or just redirect at random if that's what you want!
The choice is yours!
EDIT: Rereading your question, it looks like you're not even interested in the route - use routeStartup() as the earliest point after bootstrapping to inject your code.
I would grab the router from the front controller and call its assemble() method and then use header() :)
Regards,
Rob...
You can check the condition on "routeShutdown" method in plugin and then use $this->actionController->_helper->redirector() to redirect ;)