I believe I know how to do this, but wanted to verify with my awesome community peeps. =)
Here's an example:
I have a Controller class called 'tami', with an action 'index'.
I know that if I want someone to access that controller/action combo via an URL other than "/tami/" or "/tami/index", then I should add a route, via something like this:
Route::set('pretty_tami', 'these-are-my-initials(/<action>)')
->defaults(array(
'controller' => 'tami',
'action' => 'index',
));
But, users can still access this page via /tami/.
How can I turn off the default routing, so that the only valid routes are the ones I define?
I assume I can just remove the default route found in kohana/application/bootstrap.php. Is that correct? Or would that break something else?
I'd say exactly the same as #simshaun — either remove the default route (leaving other controllers unreachable) or check in the before() function in Controller_Tami for the uri to see if it's what you're after.
If you're using Kohana 3.1, you can now use lambda logic/anonymous functions to define your routes.
Something like this would take the extra routing logic out of the controller (which is good as we're keeping it in one place):
Route::set('default', function($uri)
{
if ($uri == 'tami' OR $uri == 'tami/index')
{
// Route not allowed by the default methods
throw new Kohana_404_Exception("Route not permitted");
}
},
'(<controller>(/<action>(/<id>)))'
);
Something I haven't yet used but it looks amazingly powerful.
I think the easiest way would be to remove the default route in your bootstrap file, yes. However, any controllers that you have not manually specified a route for can no longer be accessed.
What I would do is create a class, e.g. Controller_Derouter that Controller_Tami extends. Use the before() method in Controller_Derouter to test if the controller was accessed from the default route, and if so, throw a 404. I think you should be able to do that by comparing $this->request->controller against the first URI segment.
Edit: The solution mentioned above is unnecessary if you ever only plan on disabling the default route for just the Tami controller. If that's the case, you could just implement the before() method directly in the Tami controller.
Maybe like this?
Route::set('pretty_tami', 'these-are-my-initials/<action>')
->defaults(array(
'controller' => 'tami',
));
So there wouldn't be a default action. And you probably want to update the default route (if you still have one) with a regex to exclude tami.
Route::set('default', '(<controller>(/<action>(/<id>)))', array('controller' => '/^(?!tami)/'))
->defaults(array(
'controller' => 'welcome',
'action' => 'index',
));
Related
I recently started working with the kohana 3.3.1 framework and ran into some problems.
I want to create different routes for different entry points. Right now, this is the default route, which seems to work fine(I think):
Route::set('default', '(<controller>(/<action>(/<id>)))')
->defaults(array(
'controller' => 'welcome',
'action' => 'index',
));
When I go to the website, it displays 'hello, world!'.
I have a controller called Street, located in application/classes/Controller/Street.php.
The code to this controller is:
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Street extends Controller {
public function action_index()
{
$this->response->body('hello, street!');
}
The problem is, that I want to type /something behind the URI, and it should redirect to the defined controller, and action. But it doesn't seem to work. I get 404 error's when I type ANYTHING behind the default URI.
For routes, i use this
/**
* Set the routes. Each route must have a minimum of a name, a URI and a set of
* defaults for the URI.
*/
Route::set('test1', 'street/<id>')
->defaults(array(
'controller' => 'street',
'action' => 'index',
));
Route::set('default', '(<controller>(/<action>(/<id>)))')
->defaults(array(
'controller' => 'welcome',
'action' => 'index',
));
Any help would be gladly appreciated.
EDIT
I just tried #Darsstar 's instruction to go to /index.php/street, and it worked!
But now, when I try to go to /index.php/street/derp, I get an error saying
The requested URL derp was not found on this server.
So it's not yet working properly I guess
Since the /index.php/street version works go and read the Clean URLs tutorial if you haven't already. If you have, double check everything!
If you have just those two routes, in that order, /index.php/street/derp should have matched the route 'test1'.
The error message 'The requested URL derp was not found on this server.' says you went to /index.php/derp, not index.php/street/derp. Which would match the default route and be dispatched to Controller_Derp::action_index(), but it doesn't exist so Request_Internal::execute() throws a HTTP_Exception_404.
The default route is more of and example. The is a discussion on Kohana's issue tracker to remove it since a catchall default route is a bad practice. Routes should be specific. I recommend to remove it.
And if you think having a catchall route is a good way to catch all 404 requests, please let the Custom Error Pages tutorial prove you wrong.
I have a stat, in which many can exist for an improvement, which is one model in my about page. I initially built the page as one giant controller having silly actions like "action_editimprovementstat".
So I tried to move things into directories, so rather than everything being in "[...]/controller/about", I moved things into there perspective folders, for example: "[...]/controller/about/improvement/stat"
I changed the regex of the route, so the controller would accept slashes, which judging by the debugger, worked, because now the controller text will show up as "about/improvement/stat", unfortunately it still tells me the requested url can't be found.
So, I ask, what is the simplest way to have a hierarchical controller structure?
Here is an example of controller URLs that I would prefer:
/about
/about/internal
/about/external
/about/improvement
/about/improvement/stat
Those would also have actions, so for example:
/about/improvement/edit/6
/about/improvement/stat/delete/7
I'm willing to compromise if there are issues with ambiguity.
(Btw, I think I could manage a way if I did my own routing through a single controller, but I'm wondering if there is a better way, or if that way is well documented [so I can learn from another's experience].)
You can simply add additional variables or constant values to the route if you live.
The Kohana documentation even shows a concrete example, where an extra directory is added in front of the route, which can have only one of two given values:
Route::set('sections', '<directory>(/<controller>(/<action>(/<id>)))',
array(
'directory' => '(admin|affiliate)'
))
->defaults(array(
'controller' => 'home',
'action' => 'index',
));
Of course you can add values in the back or inbetween as well. The only requirement is that your route will always result in at least a controller and an action. But they don't actually have to exist in the url. You can specify routes that match other values and have a constant value for controller and/or action, like this:
Route::set('search', ':<query>', array('query' => '.*'))
->defaults(array(
'controller' => 'search',
'action' => 'index',
));
The greatest pitfall: It is important to understand that routes are matched in the order they are added, and as soon as a URL matches a route, routing is essentially "stopped" and the remaining routes are never tried. Because the default route matches almost anything, including an empty url, new routes must be place before it.
Maybe that is what's going wrong now?
Anyway, rather than adding trickery to match slashes, I'd rather create a route that accepts a large number of optional variables, so you could read 'urlpart1' to urlpartX' from your generic controller. That is, if you need to. The setup, of course, is to let you create different controllers for different urls, so you don't need a humongous controller with a gigantic method to decide what to do based on the url parts.
Ever since I learned Kohana, my programming experience has been greatly improved because prior to Kohana I never gave a moments thought to how my urls were constructed. In the MVC world using Pretty URLs makes you really think about what you want to do and how to go about it.
In my opinion by looking at what you are wanting to do in the examples above, it seems to me that you are thinking backwards. You said the URLS that you preferred are: /about /about/internal /about/external /about/improvement /about/improvement/stat
It seems to me that "about" is really an action, not a controller. The url "/about/" is pretty confusing because it doesn't tell me what I'm getting information about but we can let that one slide because it's probably about the site in general. "/about/internal" is pretty clear but in a lot of ways you are trying to write your urls so that they read in proper English. In reality I would write them as: /about, /internal/about, /external/about, /improvement/about, /improvement_stat/about
I'm not sure why you are resisting have several controllers unless perhaps you are setting up your controllers as template controllers and maybe you think you have to do that for each one. You dont. In general I create a controller named "page" which is my template controller. Then all other controllers extend the page controller. I can define constants and other variables in the page controller that can be used in all the controllers that extend the page controller.
But if you are really resisiting writing multple controllers, you can always write specific routes that will let you reach any controller and action that you want. For example I used a route for a comparison where I wanted up to 4 id's passed into my route. I wrote that route like this:
Route::set('thing_compare', 'thing/compare/<thing1>/<thing2>(/<thing3>(/<thing4>))')
->defaults(array(
'controller' => 'thing',
'action' => 'compare'
));
Note that thing3 and thing4 are in parens which means they are optional. Then in my controller I can get those values by doing something like:
$thing1 = $this->request->param('thing1');
But going back to the examples you gave, just write the routes something like this (assuming your controller is named "about":
Route::set('about_internal', 'about/internal')
->defaults(array(
'controller' => 'about',
'action' => 'about_internal'
));
Route::set('about_external', 'about/external')
->defaults(array(
'controller' => 'about',
'action' => 'about_external'
))
Personally I would avoid setting routes like this and really reconsider how your urls need to be setup so that it creates a sensible design strategy.
I have a route:
Route::set('foo/subdir', '<directory>/<variable>/subdir/<controller>/<action>');
I would like to route this url to the following controller/action:
/application/classes/<directory>/subdir/<controller>.php::action_<action>()
I already have and need this route too, which complicates things:
Route::set('foo', '<controller>/<variable>/<action>');
Is that possible?
Why not, as long as the default route is defined after the directory route.
Route::set('foo/subdir', '<directory>/<variable>/subdir/<controller>/<action>')
->defaults(array(
'directory' => 'default_directory',
'controller' => 'index',
'variable' => 'default_variable',
'action' => 'index',
));
Kohanas routing supports directories 'natively', there is no need to hack anything.
Please note your class names will have to include the directory name as well.
I would like to append the subdir to the directory
This will be possible in Kohana v3.3 using the new Route::filter functionality. There is currently no way to do this in Kohana 3.1 or 3.2 without modifying the Route and/or Request classes.
Use REGEXP to catch directory and subdirectory as /directory/subdirectory/controller/action
to match Route like // where regexp allows you to put / inside directory. Then make little modification in your Route class to change all / to the _
It is not tested ... yet. ;) But im about to...
If I defined 2 controllers, A.php and B.php, and in each controller I defined an index action,
how should I define Route::set in the bootstrap.php file?
The default route should do just fine:
Route::set('default', '(<controller>(/<action>(/<id>)))')
->defaults(array(
'controller' => 'welcome',
'action' => 'index',
));
Please read the documentation: http://kohanaframework.org/3.2/guide/kohana/routing
That default route will match both controller '/a' and controller '/b', and execute the index action if no other action is specified in the URI.
I have read the documentation and it's not completely clear when and why you would want to create a Route entry.
Basically, a Route entry is needed if you have a URL that does not conform to the normal /controller/action structure. If you have created controller A and controller B both with index actions, you don't need to add routes to bootstrap.php if you are always going to access those actions using the standard URL syntax:
http://www.example.com/A/index
http://www.example.com/B/index
You could leave off "index" since it is the default action if none is specified.
Let's say you want controller A to be the default site controller, meaning that you don't want to have to use A in the URL, you want to use the action right after the domain:
http://www.example.com/index
Then you do need a route to tell Kohana that any URL that is not matched by any routes you have created (or if you have none) should be handled by your default route. You would create this route in bootstrap.php:
Route::set('default', '(<controller>(/<action>(/<id>)))')
->defaults(array(
'controller' => 'A',
'action' => 'index'
));
This says that if a user goes to http://www.example.com, Kohana will use the index action of controller A. If the user goes to http://www.example.com/foo, then Kohana will use the foo action of controller A. Any URL that does not match any other controller will go to the A controller. If a user requests an action that A does not handle, he'll get a 404 exception.
You still have the B controller, so that will work fine without any route. If the user goes to http://www.example.com/B/index, Kohana knows about the B controller in the app so it will go to the index action there.
Your problem may be in .htaccess file in kohana folder.
I needed to change the "RewriteBase" to the Kohana folder ('base_url' from Kohana::init in bootstrap.php file), otherwise I landed in '404 - no object found'.
Then the default route should do just fine.
I have a set of rather static pages wich I moved to the views/pages folder. The resulting *.ctp files are editable by my customer through CushyCMS (simplistic cms perfect for dummy proof editing). However CushyCMS generated preview links that obviously don't take CakePHP into account. I would like to solve this little problem with custom routing, but can't get my head around the details..
How can I dynamically connect the url http://localhost:8888/cake125/app/views/pages/test.ctp to http://localhost:8888/cake125/pages/test?
I added the following in my routes.php:
Router::connect('/pages/test.ctp', array(
'controller' => 'pages',
'action' => 'display', 'test'));
This works ok for connecting: http://localhost:8888/cake125/pages/test.ctp to http://localhost:8888/cake125/pages/test. Somehow following snibbet doesn't do the trick:
Router::connect('/app/views/pages/test.ctp', array(
'controller' => 'pages',
'action' => 'display', 'test'));
Ideally I'd like to have a single Router::connect statement which connects all /app/views/pages/*.ctp requests to the right place.
Finally I would also like to correctly handle google search results for the old version of the site. Like so:
Router::connect('/test.html', array(
'controller' => 'pages',
'action' => 'display', 'test'));
This works ok but I'd rather have anypage.html connect to /pages/anypage. Can anyone help with this?
Thanks in advance!
First, by virtue of having Cake in a subdirectory (/cake125), I think you may need to connect the /cake125/:controller/:action, rather than how you have it. Not 100%, though; Cake might be robust enough to handle that use case. If you have weird errors, I'd check that.
On with my answer:
I think you are somewhat misunderstanding how the Router class works. You connect URLs, not relative filesystem paths, using Router::connect. By default (which you may have erased, but it's pretty simple to fix), Cake will route requests to /pages/* to the PagesController::display() function, passing it one argument (the action listed in the http request).
So, to have the pages controller map /pages/one to the app/views/pages/one.ctp element, simply make sure that the following (default, i.e. Cake normally has this setup) line is in the routes config (and make sure that lines above it do not match that pattern):
Router::connect( '/pages/:action', array( 'controller' => 'pages', 'action' => 'display', :action);
This should ensure that PagesController::display( $action ) is invoked during the request, which is (I think) what you're after.
If your CMS generates preview links that you want to correctly re-route, I'd suggest adding a new route. E.g., if your CMS generates links like http://somesite.com/cms/preview/newly_edited_file, you can route it like this:
Router::connect( '/cms/preview/:action', array( 'controller' => 'pages', 'action' => 'display', :action );
For your second question: have a default rule in your routes (make it the last rule, and have it match *). It will then be configured to route all not found requests to your controller/action pair as requested. Try this:
Router::connect( '/:action', array( 'controller' => 'pages', 'action' => 'display', :action );
Major caveat this will break your existing routes. You will need to manually add an entry for each of your existing controllers (Router::connect( '/users/:action', ...etc...). If you google around you can find some clever solutions, such as having that list generated at runtime for you. But you will need to address "normal" routing, once you've added that catch-all (and make sure your catch-all is at the end of the routing file).
Also, if you want to parse URLs like /test.html, simply add a call to Router::parseExtensions(...) so that Cake will register .html as an extension for it to parse. Check the manual on that function for more info.
As others have pointed out how CakePHP Router works, I'll leave it at that.
For the second part of your question (handling old links), I'd suggest adding this to the end of your Routes list:
Router::connect( '/:page',
array (
'controller' => 'pages',
'action' => 'display',
),
array (
'pass' => array ('page'), // to pass the page as first arg to action
'page' => '.+\.html$', // to verify that it ends with .html
)
);
You'd unfortunately have to parse out the .html yourself though
How can I dynamically connect the url http://localhost:8888/cake125/app/views/pages/test.ctp to http://localhost:8888/cake125/pages/test?
Well, the thing is, you don't. :-)
What I mean by that is, you do not connect a URL to another URL. What you really do is, you make certain URLs trigger certain Controller functions (or Actions for short) which in turn may (or may not) render certain Views. By default it's all straight forward through naming conventions. The URL /foo/bar triggers the Controller Foo's Action bar and renders the View /views/foo/bar.ctp.
The PagesController is already a special case. The URL /pages/foo triggers the Controller Pages's Action display, passes it the parameter foo, which renders the View /views/pages/foo.ctp. Notice the difference in which Action is triggered.
Since there are a lot of steps inbetween, it's not a given that a certain URL corresponds to a particular file on the hard disk. The URL /foo/bar might trigger Controller Baz' Action doh which renders the View /views/narf/glob.ctp.
This makes translating http://localhost:8888/cake125/app/views/pages/test.ctp to render the file /views/pages/test.ctp somewhere between an uncertainty and a pain in the rear.
Edit:
Having said that, the particular problem in your case is that the base URL is http://localhost:8888/cake125/app/. You can invoke a Cake app from http://localhost:8888/cake125/, http://localhost:8888/cake125/app/ or http://localhost:8888/cake125/app/webroot. All three URLs will be handled by the same file cake125/app/webroot/index.php, if you use one of the shorter URLs the request will be "forwarded" (rewritten) via .htaccess rules.
So the Route you're trying to connect, the Route that Cake sees, is actually /views/pages/test.ctp.
Actually, my mistake, this might not be the problem, but it depends on your .htaccess files and server configuration.
It doesn't seem to make much sense in a CMS though, since every newly created page would need its own rule. So I'd recommend against trying to do so and rather hack Cushy to properly construct URLs using the Cake HtmlHelper or Router::url(). Failing that, connect all URLs with a catch-all rule to some Action, parse the URL there and render the correct View "manually".
Alternatively, use .htaccess files and rewrite rules to actually rewrite the URL into a normal Cake URL, so Cake doesn't have to worry about it. As said above though, this can be very fragile.