With the Zend Framework, I am trying to build routes for a REST api on resources organized in the following pattern:
http://example.org/users/
http://example.org/users/234
http://example.org/users/234/items
http://example.org/users/234/items/34
How do I set up this with Zend_Rest_Route?
Here is how I have setup the route for the users resource (users/:id) in my bootstrap.php file:
$this->bootstrap('frontController');
$frontController = Zend_Controller_Front::getInstance();
$restRoute = new Zend_Rest_Route($frontController);
$frontController->getRouter()->addRoute('default', $restRoute);
[As far as I understand, this is a catch all route so users/324/items/34 would results in parameters set as id=324 and items=34 and everything would be mapped to the Users (front module) Model. From there I guess I could just test for the items parameter and retrieve the item #34 for user #324 on a get request.]<=== I just checked it and it doesn't seems to work like that:
Acessing /users/234/items/43 and
var_dump($this->_getAllParams());
in the get action of the rest controller results in the following output:
array(4) {
["controller"]=> string(5) "users"
["action"]=> string(3) "get"
[2]=> string(5) "items" ["module"]=> string(7) "default"]
}
Somehow both ids got lost...
Anyone?
I open sourced the solution into github and as a composer package. see https://github.com/aporat/Application_Rest_Controller_Route.
I added a new class which extends Zend_Controller_Router_Route and adds abiliy to add custom restful routes, such as
$frontController = Zend_Controller_Front::getInstance();
$frontController->getRouter()->addRoute('users-messages', new Application_Rest_Controller_Route($frontController, 'users/:user_id/messages', ['controller' => 'users-messages']));
AFAIK, there is no feature in Zend_Rest_Route which allows you to do something like this. There is a proposal but not sure when it'll be implemented. You can add this in your Bootstrap to set up this custom route.
$front = $this->getResource('FrontController');
$testRoute = new Zend_Controller_Router_Route(
'users/:user_id/items/:item_id',
array(
'controller' => 'users',
'action' => 'item',
'module' => 'default'
)
);
$front->getRouter()->addRoute('test', $testRoute);
user_id or item_id become available in itemAction in UsersController as parameters:
$user_id = $this->_request->getParam('user_id');
Zend_Rest_Route maps the first parameter after the controller name to 'id' only when there is 1 parameter.
Best solution I've come up with is to subclass Zend_Rest_Route and override the match() function. Copy Zend_Rest_Route's match function, but add the following just before the "Digest URI Params" comment (about 60 lines in).
if($pathElementCount > 1 && $path[0] != 'id') {
$params['id'] = urldecode($path[0]);
array_shift($path);
}
That should give you the functionality you're looking for.
Related
I've been writing a project since some time and I've used the default routing, the :module\:controller:\:action.
After some time, I've added some routers to my config like:
resources.router.routes.page.route = "page/:slug"
resources.router.routes.page.defaults.module = "default"
resources.router.routes.page.defaults.controller = "pages"
resources.router.routes.page.defaults.action = "view"
resources.router.routes.page.defaults.slug = ""
But, after that, when I click on some link generated by view URL helper with one of the new routes all other links ignore some of given paramters. Example, I've got route:
resources.router.routes.project.route = "project/:slug"
resources.router.routes.project.defaults.module = "projects"
resources.router.routes.project.defaults.controller = "projects"
resources.router.routes.project.defaults.action = "view"
resources.router.routes.project.defaults.slug = ""
If I go to a link /project/test then link like this:
$this->url(
array('module' => 'admin', 'action' => 'list-users', 'controller' => 'users')
, null,true
);
will point to "/project"
Is there any possibility to maintain the default routing on top of custom routes? Can I add some default router that will work the same as the default one? It's probably something simple but I maybe missed the point. Thanks for all the help.
I've added something like this but with no effect:
resources.router.routes.default.route = ":module/:controller/:action"
resources.router.routes.default.defaults.module = "default"
resources.router.routes.default.defaults.controller = "pages"
resources.router.routes.default.defaults.action = "view"
resources.router.routes.default.defaults.slug = ""
In order for you to set your custom routing, you need to get the router component and pass your routes into it.
This is how I did mine in a project I am working on. In your Bootstrap class, you create the following function
protected function _initRoutes()
{
$this->bootstrap('frontcontroller');
$front = Zend_Controller_Front::getInstance();
$router = $front->getRouter();
$myRoutes = new Zend_Config_Ini(APPLICATION_PATH . '/configs/routes.ini','production');
$router->addConfig($myRoutes,'routes');
This calls the front controller and gets the router from it. I then pass my routes config into it.
I hope this answers your question.
The problem is that the options for url helper are as follows:
url($urlOptions, $name, $reset)
Therefore, when you set $name to null, current route ('project') is used. Not event setting $reset to true will help. Replace null with 'default' and it should work.
So I'm trying to install and get running Kohana. I am very new to it and frameworks in general (though I have used CakePHP a little bit).
Anyways...in my bootstrap file I have this:
// GET PARAMS -- This basically splits domain.com/kohana/controller/action/param1/etc
// into: controller | action | param1 | etc
$requestURI = explode('/', $_SERVER['REQUEST_URI']);
$scriptName = explode('/',$_SERVER['SCRIPT_NAME']);
for($i= 0;$i < sizeof($scriptName);$i++){
if ($requestURI[$i] == $scriptName[$i]){
unset($requestURI[$i]);
}
}
$param = array_values($requestURI);
$controller = # $param[0];
$action = # $param[1];
$param1 = # $param[2];
$param2 = # $param[3];
$param3 = # $param[4];
$param4 = # $param[5];
$param5 = # $param[6];
$param6 = # $param[7];
Now, I want to connect to my database and basically see if the first param aka $param[0] aka $controller is equal to one of my pages "categories" (corresponds to my categories_pages table). If it IS a category then I want to use the default "categories" controller otherwise, the controller should be whatever $param[0] is. This means if I go to domain.com/kohana/movies or domain.com/kohana/games it will display the categories controller otherwise domain.com/kohana/users will display the users controller.
$db = Database::instance();
$getiscategory = DB::select('*')->from('categories_pages')->where('directory', '=', $controller)->execute();
$is_category = $getiscategory->count();
if($is_category){
$controller = "categories";
}
$controller = (empty($controller)) ? 'index' : $controller;
$action = (empty($action)) ? 'index' : $action;
What I wanted to accomplish above works. If I echo $is_category I see that the value 1 is returned when there is a category match and if I echo $controller, I see that "categories" is set to be the controller.
Here is my code for the actual routing method...
Route::set(
'custom',
'(<controller>(/<action>(/<param1>)(/<param2>(/<param3>(/<param4>(/<param5>(/<param6>)))))))'
)->defaults(array(
'controller' => $controller,
'action' => $action,
'param1' => $param1,
'param2' => $param2,
'param3' => $param3,
'param4' => $param4,
'param5' => $param5,
'param6' => $param6,
));
Unfortunately, I'm not sure where it's routing to. As I mentioned $controller is returned previously as categories which is correct but yet I receive the error message "HTTP_Exception_404 [404]: The Requested URL $param[0] (movies or games, etc) was not found on this server."
Keep in mind I do not have a controller class for $param[0] if it matches a category because I want to use the "categories" controller class. If I go to domain.com/kohana/categories it works fine.
Anyone have any ideas/know a work-around?
Addition #1
I figured out that even though the categories controller is called and the correct action is called, it still is requiring the "shows" controller to display. I added a shows controller with the very basic info (template, content, etc) and it showed correctly. Is there a work around in the routing class to make the designated controller show? Like I said, I tell it what controller to go to and it acknowledges it but it doesn't actually go to it.
You're overcomplicating things I think. Also your code would force a limit of 6 parameters on every request.
I'll probably be easier to use two different routes, and if it doesn't match the first, then it can fall back to the second.
Route::set(
'categories',
'(<category>(/<action>(/<param1>)(/<param2>(/<param3>(/<param4>(/<param5>(/<param6>)))))))',
array('category' => '(movies|games)')
)->defaults(array(
'controller' => 'category',
'action' => 'index',
));
Route::set(
'users',
'<username>(/<action>(/<param1>)(/<param2>(/<param3>(/<param4>(/<param5>(/<param6>))))))'
)->defaults(array(
'controller' => 'users',
'action' => 'index',
));
If you only have one or two categories you could build them into the regex string, with some caching of course.
Otherwise, look up lambda routes, they're the means by which you can have dynamic routes like this.
In my layout-script I wish to create a link, appending [?|&]lang=en to the current url, using the url view helper. So, I want this to happen:
http://localhost/user/edit/1 => http://localhost/user/edit/1?lang=en
http://localhost/index?page=2 => http://localhost/index?page=2&lang=en
I have tried the solution suggested Zend Framework: Append query strings to current page url, accessing the router directly, but that does work.
My layout-script contains:
English
If the default route is used, it will append /lang/en, but when another route is used, nothing is appended at all.
So, is there any way to do this with in Zend without me having to parse the url?
Edit
Sorry for my faulty explanation. No, I haven't made a custom router. I have just added other routes. My bad. One route that doesn't work is:
$Routes = array(
'user' => array(
'route' => 'admin/user/:mode/:id',
'defaults' => array('controller' => 'admin', 'action' => 'user', 'mode'=>'', 'id' => 0)
)
);
foreach( $Routes as $k=>$v ) {
$route = new Zend_Controller_Router_Route($v['route'], $v['defaults']);
$router->addRoute($k, $route);
}
Upd:
You must add wildcard to your route or define 'lang' parameter explicitly.
'admin/user/:mode/:id/*'
Additionally, according to your comment, you can do something like this:
class controllerplugin extends Zend_Controller_Plugin_Abstract
{
function routeShutdown($request)
{
if($request->getParam('lang', false) {
//store lang
Zend_Controller_Front::getInstance()->getRouter()->setGlobalParam('lang', NULL); //check if this will remove lang from wildcard parameters, have no working zf installation here to check.
}
}
}
I bumped into a problem and I can't seem to find a good solution to make it work. I have to make some dynamic routes into a Zend Framework project. I'll explain shortly what my problem is:
I need to have dynamic custom routes that "extend" the default route (module/controller/action/params). The project I'm working for has several partners and the routes have to work with those.
To store the partners I've made a static class and it looks like this.
<?php
class App_Partner
{
static public $partners = array(
array(
'name' => 'partner1',
'picture' => 'partner1.jpg'
),
array(
'name' => 'partner2',
'picture' => 'partner2.jpg'
),
array(
'name' => 'partner3',
'picture' => 'partner3.jpg'
)
);
static public function routePartners() {
$partners = array();
foreach(self::$partners as $partner) {
array_push($partners, strtolower($partner['name']));
}
$regex = '(' . implode('|', $partners) . ')';
return $regex;
}
}
So App_Partner::routePartners() return me a string like (partner1|partner2|partner3) which I use to create the right routes. My goal is to have the custom routes for each partner for every route I have set in the Bootstrap. So if I have a route add-product.html set I want it to work for each partner as partner1/add-product.html, partner2/add-product.html and partner3/add-product.html.
Also, partner1/, partner2/, partner3 should route to default/index/index.
In fact, I made this thing to work using routes like the one below.
<?php
$routeProposal = new Zend_Controller_Router_Route_Regex(
App_Partner::routePartners() . '?/?proposals.html',
array(
'module' => 'default',
'controller' => 'proposal',
'action' => 'index',
'page' => 1
),
array( 1 => 'partner'),
"%s/proposals.html"
);
$router->addRoute('proposal', $routeProposal);
The problem
The above route works fine if I use a partner in the request URI, but if I don't, I get double slashes like public//proposals.html because of the reverse route set in the route above to be "%s/proposals.html". I can't seem to find a way to avoid this reverse route because I build my urls using the url view helper and if the reverse route isn't set I get an exception stating this.
I also need the routes to work without a partner set, which will be the default way (add-product.html, proposals.html etc).
From your description, it seems like you're looking for a zend router chain, where your partner is an optional chain.
Here's a similar question, but using a hostname route : Zend Framework: get subdomain parameter from route. I adapted it to solve your problem, just put the following in your Bootstrap.php to initialize the routing :
protected function _initRoute()
{
$this->bootstrap('FrontController');
$router = $this->getResource('FrontController')->getRouter();
// Default route
$router->removeDefaultRoutes();
$defaultRoute = new Zend_Controller_Router_Route(
':controller/:action/*',
array(
'module' => 'default',
'controller' => 'index',
'action' => 'index',
)
);
$router->addRoute('default', $defaultRoute);
$partnerRoute = new Zend_Controller_Router_Route(
':partner',
array('partner' => 'none'),
array('partner' => '^(partner1|partner2|partner3)$')
);
$router->addRoute('partner', $partnerRoute->chain($defaultRoute));
}
Change as you see fit. In your controllers you will only get a value for the partner parameter if it was actually specified AND valid (you will get a routing error if the partner doesn't exist)...
I use a similar process to detech lang, in my route (but with a ini file).
You can use a default value for you partners parameter to make the route working without partner, and add a ? to your regex.
But actually, I don't know how to avoid the double //...
Hope that helps.
EDIT: For your information, here is a simplified version of my route with language:
routes.lang.type = "Zend_Controller_Router_Route"
routes.lang.route = "lang/:language/*"
routes.lang.reqs.language = "^(en|fr|nl|de)?$"
routes.lang.defaults.language = none
routes.lang.defaults.module = default
routes.lang.defaults.controller = index
routes.lang.defaults.action = language
I'm looking to setup a custom route which supplies implicit parameter names to a Zend_Application. Essentially, I have an incoming URL which looks like this:
/StandardSystems/Dell/LatitudeE6500
I'd like that to be mapped to the StandardsystemsController, and I'd like that controller to be passed parameters "make" => "Dell" and "model" => "LatitudeE6500".
How can I setup such a system using Zend_Application and Zend_Controller_Router?
EDIT: I didn't explain myself all that clearly I guess -- if make and model aren't present I'd like to redirect the user to another action on the StandardsystemsController. currently, using Ballsacian1's answer with this in application.ini:
resources.router.routes.StandardSystem.route = "/StandardSystem/:make/:model"
resources.router.routes.StandardSystem.defaults.controller = "StandardSystem"
resources.router.routes.StandardSystem.defaults.action = "system"
resources.router.routes.StandardSystem.defaults.make = ""
resources.router.routes.StandardSystem.defaults.model = ""
resources.router.routes.StandardSystemDefault.route = "/StandardSystem"
resources.router.routes.StandardSystemDefault.defaults.controller = "StandardSystem"
resources.router.routes.StandardSystemDefault.defaults.action = "index"
You would first instantiate a new Zend_Controller_Router_Route to create your route.
$stdsys_route = new Zend_Controller_Router_Route(
'/StandardSystems/:make/:model',
array(
'controller' => 'StandardsystemsController',
'action' => 'myaction'
);
);
This route then needs to be added to your router.
$front_controller = Zend_Controller_Front::getInstance();
$front_controller->getRouter()->addRoute('stdsys', $stdsys_route);
Now when you dispatch, the route should take effect.
References:
http://framework.zend.com/manual/en/zend.controller.router.html
Resources:
resources.router.routes.StandardSystems.route = "/StandardSystems/:make/:model"
resources.router.routes.StandardSystems.defaults.controller = "standardsystems"
resources.router.routes.StandardSystems.defaults.action = "index"