I am trying to use prefix routing in CakePHP3. I added the following lines to /config/routes.php.
Router::prefix("admin", function($routes) {
// All routes here will be prefixed with ‘/admin‘
// And have the prefix => admin route element added.
$routes->connect("/",["controller"=>"Tops","action"=>"index"]);
$routes->connect("/:controller", ["action" => "index"]);
$routes->connect("/:controller/:action/*");
});
After that, I created /src/Controller/Admin/QuestionsController.php like below.
<?php
namespace App\Controller\Admin;
use App\Controller\AppController;
class QuestionsController extends AppController {
public function index() {
//some code here
}
}
?>
Finally I tried to access localhost/app_name/admin/questions/index, but I got an error saying, Error: questionsController could not be found. However, when I capitalize the first letter of controller name(i.e. localhost/app_name/admin/Questions/index), it is working fine. I thought it is weird because without prefix, I can use controller name whose first character is not capitalized.
Is this some kind of bug?
In Cake 3.x, routes do not inflect by default anymore, instead you'll have to explicitly make use of the InflectedRoute route class, as can for example bee seen in the default routes.php app configuration:
Router::scope('/', function($routes) {
// ...
/**
* Connect a route for the index action of any controller.
* And a more general catch all route for any action.
*
* The `fallbacks` method is a shortcut for
* `$routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'InflectedRoute']);`
* `$routes->connect('/:controller/:action/*', [], ['routeClass' => 'InflectedRoute']);`
*
* You can remove these routes once you've connected the
* routes you want in your application.
*/
$routes->fallbacks();
});
You custom routes do not specify a specific route class, so the default Route class is being used, while the fallback routes make use of inflected routing, and that is why it's working without prefix.
So either use capitalized controller names in your URL, or use a route class like InflectedRoute that transforms them properly:
Router::prefix('admin', function($routes) {
// All routes here will be prefixed with ‘/admin‘
// And have the prefix => admin route element added.
$routes->connect(
'/',
['controller' => 'Tops', 'action' => 'index']
);
$routes->connect(
'/:controller',
['action' => 'index'],
['routeClass' => 'InflectedRoute']
);
$routes->connect(
'/:controller/:action/*',
[],
['routeClass' => 'InflectedRoute']
);
});
See also http://book.cakephp.org/3.0/en/development/routing.html#route-elements
Related
I'm creating a plugin for my application (using CakePHP 2.6.0) that allows users to login into a user area using the same model as for the admin area, so I'm trying to get the same type of URI scheme as the admin area e.g. /admin/users/login but then for /special/users/login. I have the following route in my plugin's Config/routes.php and added the 'special' prefix to the 'Routing.prefixes' configuration:
Router::connect('/special/:controller/:action', array(
'special' => true,
'prefix' => 'special',
'plugin' => 'Special',
'controller' => ':controller',
'action' => ':action',
));
With above route and entering the following url /special/users/login it would make sense to me right now if Cake went for Plugin/Controller/SpecialUsersController.php (considering namespace conflicts with the main application's UsersController) but instead I get an error Error: Create the class UsersController.
Is there a built-in way for it to load a prefixed controller (without changing the url) based on the plugin? Or is there a better way to neatly extend my main application? Am I going about this the wrong way?
I could not find a built-in way for it to work as I wanted to so I changed the way URL strings were parsed using a custom route class in my app's Routing/Route/ folder:
App::uses('CakeRoute', 'Routing/Route');
/**
* Plugin route will make sure plugin routes get
* redirected to a prefixed controller.
*/
class PluginRoute extends CakeRoute {
/**
* Parses a string URL into an array.
*
* #param string $url The URL to parse
* #return bool False on failure
*/
public function parse($url) {
$params = parent::parse($url);
if($params && !empty($params['controller']) && !empty($params['plugin'])) {
$params['controller'] = $params['plugin'] . ucfirst($params['controller']);
}
return $params;
}
}
And then setting up my plugin routes as:
App::uses('PluginRoute', 'Routing/Route');
Router::connect('/special/:controller/:action', array(
'special' => true,
'prefix' => 'special',
'plugin' => 'Special',
'controller' => ':controller',
'action' => ':action',
), array(
'routeClass' => 'PluginRoute'
));
This resulted in /special/users/login creating the controller I wanted Plugin/Controller/SpecialUsersController.php, no side effects so far. Extending the main application's UsersController is a different story though.
Maybe someone else has use of this or knows a better solution?
I have following route added to routes.php in the end.
Router::connect('/:sellername/:itemtitle',
array('controller' => 'items', 'action' => 'view_item_detail'),
array(
'pass' => array('sellername','itemtitle'),
'sellername' => '[a-zA-Z0-9_-]+',
'itemtitle' => '[a-zA-Z0-9_-]+',
)
);
So this matches the dynamic urls like http://example.com/john/title-of-an-item
Problem is this also matches every other url like http://example.com/members/signin even though there's a MembersController controller and signin action in it.
I can fix it using following route entry.
Router::connect(
'/members/:action',
array('controller' => 'members')
);
But it's very tedious to add every route like above.
Doesn't existing matching controller names are prioritized while making a match?
Do order of routes in routes.php matter?
Custom Route classes is to help you
Custom route classes allow you to extend and change how individual routes parse requests and handle reverse routing. A route class should extend CakeRoute and implement one or both of match() and/or parse(). parse() is used to parse requests and match() is used to handle reverse routing.
You can use a custom route class when making a route by using the routeClass option, and loading the file containing your route before trying to use it:
Router::connect(
'/:slug',
array('controller' => 'posts', 'action' => 'view'),
array('routeClass' => 'SlugRoute')
);
This route would create an instance of SlugRoute and allow you to implement custom parameter handling.
custome routing class let you impliment anything
But personal opinion is to user a static and meaning full text in the url that diffrenciate it from the rest.
I want to define master routing for cakephp somthing like this.
Router::connect('/:lang/:plugins/:controller/:action/*', array('lang' => 'eng', 'controller' => 'index', 'action' => 'index', 'plugin' => null), array('lang' => '[a-z]{3}'));
but default not works. when i type these errors disappear:
/ Error: Controller could not be found.
/eng Error: EngController could not be found.
/eng/pages Error: EngController could not be found.
What the route errors are saying is that the controller that you're asking it to use doesn't exist. I'm assuming you're using CakePHP 2, so make sure your controller filename is like this:
EngController.php
And inside that controller file it is similar to this:
<?php
App::uses('AppController', 'Controller');
/**
* Eng Controller
*
*/
class EngController extends AppController {
/**
* Scaffold
*
* #var mixed
*/
public $scaffold;
}
EDIT:
I had another look at the route, and you have 'lang' => 'eng' at the start of the route. The errors you're getting are from the route thinking that eng is a controller. Remove this section altogether and it should fix it.
I've created a custom route class and I want to be able to pass in settings/options to the constructor so that it's configurable. Can this be done?
Documentation for Custom Route Classes:
http://book.cakephp.org/2.0/en/development/routing.html#custom-route-classes
My custom route class:
https://github.com/Signified/CakePHP-Model-Route-Class
You can probably just pass any settings/options you might have in the options of your Router::connect function.
App::import('Lib', 'ModelRoute');
Router::connect('/', array('controller' => 'pages', 'action' => 'display'),
Array('routeClass' => 'ModelRoute',
'someMoreOptions' => 'OptionValue' ));
Then you can retrieve the key someMoreOptions in your constructor
public function __construct($settings = array())
{
$this->settings = Set::merge($this->settings, $settings);
// Now you can do something with the option passed.
if(isset($this->settings['someMoreOptions'])
DoSomethingWith($this->settings['someMoreOptions']);
}
I need to create routes that include a colon to produce URLs like http://app.com/prjct:a9b5c. Obviously it's currently simple to use a slash instead with the default routing.
$SLUG = array('slug' => '[-_A-Za-z0-9]+');
Router::connect('/prjct/:slug', array('controller' => 'projects', 'action' => 'show'), $SLUG);
But routes specifications use the colon character as a special indicator, which interferes with my naive attempt to replace the second slash above with another colon.
How do I use colons in this case for a route?
You can use named parameter as explained in CakePHP Cookbook. Write code below in your app/config/routes.php:
// Parse only the 'prjct' parameter if the current action is 'show' and the controller is 'projects'.
Router::connectNamed(array('prjct' => array('action' => 'show', 'controller' => 'projects')));
// Then set default route to controller 'projects' and action 'show
Router::connect('/', array('controller' => 'projects', 'action' => 'show'));
In your projects_controller.php :
function show(prjct = null) {
// Check if prjct match the pattern
$pattern = '[-_A-Za-z0-9]+';
if(!preg_match($pattern, prjct)){
// Redirect somewhere else
}
// Rest of your code here
}
I think this is indeed out of scope for simple routes. I see two options:
Use a custom route parsing class, as described here. There isn't a whole lot of documentation on the topic, but you can extend the existing class and play around with it to get a hang of what it's doing. Then customize it to your needs.
class MyRoute extends CakeRoute {
public function parse($url) {
debug($url); // input
$route = parent::parse($url);
debug($route); // output
return $route;
}
}
Route these URLs with a catch-all route to a controller, where the parameter will be available as a named parameter in $this->params['named']. Do what you need to do there.