How to create a custom router in Zend-Framework? - php

I am using a custom Router to enable pages like:
mytutorialsite.com/category/:categoryname
# added to application.ini
resources.router.routes.categorynameOnCategory.route = /category/:categoryname
resources.router.routes.categorynameOnCategory.defaults.module = default
resources.router.routes.categorynameOnCategory.defaults.controller = category
resources.router.routes.categorynameOnCategory.defaults.action = categoryname
I also have database table 'categories' in which all categories are stored. For example, let's say the following categories are currently stored in my database:
- html
- css
- js
- php
This means, the following pages exist:
mytutorialsite.com/category/html
mytutorialsite.com/category/css
mytutorialsite.com/category/js
mytutorialsite.com/category/php
But when you visit a page with a categoryname that is not listed in the database, like:
mytutorialsite.com/category/foo
You should get a 404 Page Does Not Exist message.
How do I achieve that?

I think you mean with categoryname as action in your route the :categoryname should be used as an action? There are two methods you can use. First is you add only the routes to the router where categories exist. When category/foo is requested the router won't recognize the route and throw the 404 error.
The second option is you catch all category/* routes and inside your action you check if the category exists.
For the first option, add a frontController plugin with a routeStartup function. In this hook you can do:
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
// Get the router
$router = Zend_Controller_Front::getInstance()->getRouter();
// Fetch all your categories
$category = new Application_Model_Category;
$categories = $category->fetchAll();
// Loop and add all individual categories as routes
foreach ($categories as $category) {
$route = 'category/:' . $category->name;
$params = array(
'module' => 'default',
'controller' => 'category',
'action' => $category->name
);
$route = new Zend_Controller_Router_Route($route, $params);
$router->addRoute($category->name, $route);
}
}
The other method is more simple for the route. In your application.ini:
resources.router.routes.category.route = "category/:action"
resources.router.routes.category.module = "default"
resources.router.routes.category.controller = "category"
Now all requests from category/SOMETHING will go to the default module, category controller. The dispatcher checks if the action SOMETHING exists. When it does, it executes the action. When not, a Zend_Controller_Action_Exception ("action does not exist") is throw.
So in short, both methods work. With the first you get more control. The second is more simple but when e.g. an editAction, addAction or removeAction in the categoryController exist, they can be triggered as well (so be careful with that method).
PS. Of course, the routeStartup function should have a caching mechanism to prevent a database query on each request.

Related

How to remove action part in zend framework URLs

I`m using zend framework and my urls are like this :
http://target.net/reward/index/year/2012/month/11
the url shows that I'm in reward controller and in index action.The rest is my parameters.The problem is that I'm using index action in whole program and I want to remove that part from URL to make it sth like this :
http://target.net/reward/year/2012/month/11
But the year part is mistaken with action part.Is there any way ?!!!
Thanks in advance
Have a look at routes. With routes, you can redirect any URL-format to the controller/action you specify. For example, in a .ini config file, this will do what you want:
routes.myroute.route = "reward/year/:myyear/month/:mymonth"
routes.myroute.defaults.controller = reward
routes.myroute.defaults.action = index
routes.myroute.defaults.myyear = 2012
routes.myroute.defaults.mymonth = 11
routes.myroute.reqs.myyear = "\d+"
routes.myroute.reqs.mymonth = "\d+"
First you define the format the URL should match. Words starting with a colon : are variables. After that you define the defaults and any requirements on the parameters.
you can use controller_plugin to control url .
as you want create a plugin file (/library/plugins/controllers/myplugin.php).
then with preDispatch() method you can get requested url elements and then customize that for your controllers .
myplugin.php
class plugins_controllers_pages extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$int = 0;
$params = $this->getRequest()->getParams();
if($params['action'] != 'index' ) AND !$int)
{
$int++;
$request->setControllerName($params['controller']);
$request->setActionName('index');
$request->setParams(array('parameter' => $params['action']));
$this->postDispatch($request);
}
}
}

How to createe a URL string based on a named route and keep parameters

I need to create urls for category switching, switching category should reset page to 1st and change cat name but keep rest of url params.
Example of url:
http://example.com/cat/fruits/page/3/q/testquery/s/date/t/kcal/mine/26/maxe/844/minb/9/mint/4/maxt/93/minw/7/minbl/6/maxbl/96
Urls can contain many different params but category name and page should be always first, category without page should work too and show 1st page.
Currently I have defined two routes and I'm using Zend's url helper with named route but it resets params and as end resuls I have /cat/cat_name/page/1 url.
$category_url = $view->url(array('category'=>$list_item['url'],'page'=>1),'category',FALSE)
resources.router.routes.category_main.route = "/cat/:category/*"
resources.router.routes.category_main.defaults.module = "ilewazy"
resources.router.routes.category_main.defaults.controller = "index"
resources.router.routes.category_main.defaults.action = "results"
resources.router.routes.category_main.defaults.category =
resources.router.routes.category_main.defaults.page = 1
resources.router.routes.category.route = "/cat/:category/page/:page/*"
resources.router.routes.category.defaults.module = "ilewazy"
resources.router.routes.category.defaults.controller = "index"
resources.router.routes.category.defaults.action = "results"
resources.router.routes.category.defaults.category =
resources.router.routes.category.defaults.page = 1
Do I need to create custom method for assembling url in this case?
Things I would try:
Add module, controller and action params in $urlOptions (it's the
first array you pass to the view helper)
instead of 'category' route name try null and see what that does for
you.
Try removing this line
"resources.router.routes.category.defaults.category = "
Please specify what version are of zf are you using.
Solution is very simple, one just have to pass all current request params to url helper
(and optionaly overwrite/add some of them, page and category in my case), last variable is set to FALSE to prevent route resetting.
$view->url(
array_merge(
$params,
array('category' => $list_item['url'],
'page' => 1)
),
'category_main',
FALSE
);

Default routing in Zend Framework after defining some routes

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.

Kohana: Requested URL was Not Found

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.

CakePHP view method using post title as url

I have the following link structure for my portfolio:
<?php echo $this->Html->link($post['Portfolio']['title'], array('controller' => 'portfolio', 'action' => 'view', Inflector::slug($post['Portfolio']['title'])), array('title' => $post['Portfolio']['title'])); ?>
Which gives urls like: http://driz.co.uk/portfolio/view/Paperview_Magazine
However how do I get my controller to show the item based on the title?
So far I have this but have not been able to get it to work and just get a blank page (so I ALSO need to check the format is correct and that their is a relevant item)
function view ( $title )
{
$posts = $this->Portfolio->find('first', array('conditions' => array('Portfolio.title' => $title)
));
if (empty($title))
{
$this->cakeError('error404');
}
$this->set(compact('posts'));
}
#Ross suggested that you search using Portfolio.slug so here's how you could do this:
Add a field to your database table called slug. You'll most likely want a VARCHAR with sufficient length to accommodate the slug.
When you create or update a "Portfolio" record, use the Inflector::slug method to generate a slug and save it to your database. You could always do this in the model's beforeSave event or if you prefer, in the controller before saving the data.
Update the find call to look for Portfolio.slug instead of Portfolio.title.
Unfortunately, there's no way to reverse the Inflector::Slug function as it removes certain characters like apostrophes, quotes, parentheses, etc. which is why you need to save the slug to your database if you want to search for it.
Here's how you could use the beforeSave event in your model:
public function beforeSave(array $options = array())
{
// If the title is not empty, create/update the slug.
if ( ! empty($this->data[$this->alias]['title'] )
$this->data[$this->alias]['slug'] = Inflector::slug($this->data[$this->alias]['title']);
// Returning true is important otherwise the save or saveAll call will fail.
return true;
}

Categories