I'm writing a small routing system for a project. It's not perfect and it's a custom solution that will map the url to their templates if requested from the user. I want to generate a dynamic page based on an unique id for each event inserted inside the database from the user. So if the user request the event 1234 it will get a page with the event detail at the url https://mysitedomain.com/event/1234. I need to understand how to achieve this with my code, I'm using a front controller and red bean as ORM to access the database.
Here is the code of my router. Any suggestion will be appreciated. for now I'm only able to serve the templates.
<?php
namespace Router;
define('TEMPLATE_PATH', dirname(__DIR__, 2).'/assets/templates/');
class Route {
private static $assets = ['bootstrap' => 'assets/css/bootstrap.min.css',
'jquery' => 'assets/js/jquery.min.js',
'bootstrapjs' => 'assets/js/bootstrap.min.js',
];
public static function init()
{
if( isset($_SERVER['REQUEST_URI']) ){
$requested_uri = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH);
if( $requested_uri === '/' ){
echo self::serveTemplate('index', self::$assets);
}
elseif( $requested_uri != '/' ){
$requested_uri = explode('/', $_SERVER['REQUEST_URI']);
if( $requested_uri[1] === 'event' ){
echo self::serveTemplate('event', self::$assets, ['event_id' => 001] );
}
else{
echo self::serveTemplate($view, self::$assets);
}
}
}
}
private static function serveTemplate(string $template, array $data, array $event_id = null)
{
if( !is_null($event_id) ){
$data[] = $event_id;
ob_start();
extract($data);
require_once TEMPLATE_PATH."$template.php";
return ob_get_clean();
}
else{
ob_start();
extract($data);
require_once TEMPLATE_PATH."$template.php";
return ob_get_clean();
}
}
}
?>
Writing a router from scratch is a little complex, you have to play a lots with regular expression to accommodate various scenario of requested url and your router should handle HTTP methods like POST, GET, DELETE, PUT and PATCH.
You may want to use existing libraries like Fast Route, easy to use and it's simplicity could give you idea how it is created.
I have been trying to get the index() method of the Home controller using the altorouter but am unable to. I have searched a few places but I could not find any help.
Here is the index.php
<?php
include 'system/startup.php';
include 'library/AltoRouter.php';
// Router
$router = new AltoRouter();
// Change here before upload
$router->setBasePath('/demo');
$router->map('GET|POST','/', 'home#index', 'home');
// match current request
$match = $router->match();
if( $match && is_callable( $match['target'] ) ) {
call_user_func_array( $match['target'], $match['params'] );
} else {
// no route matched
header( $_SERVER["SERVER_PROTOCOL"] . ' 404 Not Found');
}
the home controller in the catalog>controller directory.
<?php
class home {
public function index() {
echo 'home';
}
}
Can anyone using or who ever used this altorouter guide?.
P.S. I have the autoload function in the startup.php file (included at the top of the index.php)
This is old thread but this can help you. You need match request in different way:
<?php
include 'system/startup.php';
include 'library/AltoRouter.php';
// Router
$router = new AltoRouter();
$router->setBasePath('/demo');
$router->map('GET|POST','/', 'home#index', 'home');
$match = $router->match();
if ($match === false) {
// here you can handle 404
} else {
list( $controller, $action ) = explode( '#', $match['target'] );
if ( is_callable(array($controller, $action)) ) {
call_user_func_array(array($controller,$action), array($match['params']));
} else {
// here your routes are wrong.
// Throw an exception in debug, send a 500 error in production
}
}
I am trying to use a router (AltoRouter) for the first time and am unable to call any page.
Web folder structure
The Code
Index.php
require 'lib/AltoRouter.php';
$router = new AltoRouter();
$router->setBasePath('/alto');
$router->map('GET|POST','/', 'home#index', 'home');
$router->map('GET|POST','/', 'display.php', 'display');
$router->map('GET','/plan/', 'plan.php', 'plan');
$router->map('GET','/users/', array('c' => 'UserController', 'a' => 'ListAction'));
$router->map('GET','/users/[i:id]', 'users#show', 'users_show');
$router->map('POST','/users/[i:id]/[delete|update:action]', 'usersController#doAction', 'users_do');
// match current request
$match = $router->match();
if( $match && is_callable( $match['target'] ) ) {
call_user_func_array( $match['target'], $match['params'] );
} else {
// no route was matched
header( $_SERVER["SERVER_PROTOCOL"] . ' 404 Not Found');
}
I have a file named plan.php (display plan) in the plan folder and the hyperlink that I am trying is
Plan <?php echo $router->generate('plan'); ?>
which does not work.
Can you help?
You cannot call plan.php by passing plan.php as an argument to match function
Check examples at http://altorouter.com/usage/processing-requests.html
If you want to use content from plan.php
you should use map in the following format
$router->map('GET','/plan/', function() {
require __DIR__ . '/plan/plan.php';
} , 'plan');
to the file plan/plan.php add echo 'testing plan';
Also, double check that your .htaccess file contains
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]
Also, if you set base path with $router->setBasePath('/alto'); your index.php files should be placed in the alto directory so your url would be in that case http://example.com/alto/index.php
Working example:
require 'lib/AltoRouter.php';
$router = new AltoRouter();
$router->setBasePath('/alto');
$router->map('GET','/plan/', function( ) {
require __DIR__ . '/plan/plan.php';
} , 'plan');
// match current request
$match = $router->match();
if( $match && is_callable( $match['target'] ) ) {
call_user_func_array( $match['target'], $match['params'] );
} else {
// no route was matched
header( $_SERVER["SERVER_PROTOCOL"] . ' 404 Not Found');
}
then this will work just fine
Plan <?php echo $router->generate('plan'); ?>
I am using CodeIgniter2. I am using routing to route url segments to a controller and method.
This seems to work. My pages load as expected i.e. the url goes to the correct method to get the page info from the database, come back and display the correct page. My routes.php relevant code is:
$route['default_controller'] = "content";
$route['en/(:num)/(:any)'] = "content/en/$1";
$route['de/(:num)/(:any)'] = "content/de/$1";
$route['es/(:num)/(:any)'] = "content/es/$1";
$route['it/(:num)/(:any)'] = "content/it/$1";
$route['ar/(:num)/(:any)'] = "content/ar/$1";
$route['404_override'] = '';
HOWEVER instead of that displayed page showing a http status of 200, it shows a http status of 404 ... I have no idea why.
I suspect it is to do with a MY_Router.php file i have to give a custom error page but i can't work out what's going on.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class MY_Router extends CI_Router {
var $error_controller = 'error';
var $error_method_404 = 'error_404';
function My_Router()
{
parent::CI_Router();
}
// this is just the same method as in Router.php, with show_404() replaced by $this->error_404();
function _validate_request($segments)
{
// Does the requested controller exist in the root folder?
if (file_exists(APPPATH.'controllers/'.$segments[0].EXT))
{
return $segments;
}
// Is the controller in a sub-folder?
if (is_dir(APPPATH.'controllers/'.$segments[0]))
{
// Set the directory and remove it from the segment array
$this->set_directory($segments[0]);
$segments = array_slice($segments, 1);
if (count($segments) > 0)
{
// Does the requested controller exist in the sub-folder?
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].EXT))
{
return $this->error_404();
}
}
else
{
$this->set_class($this->default_controller);
$this->set_method('index');
// Does the default controller exist in the sub-folder?
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT))
{
$this->directory = '';
return array();
}
}
return $segments;
}
// Can't find the requested controller...
return $this->error_404();
}
function error_404()
{
$this->directory = "";
$segments = array();
$segments[] = $this->error_controller;
$segments[] = $this->error_method_404;
return $segments;
}
function fetch_class()
{
// if method doesn't exist in class, change
// class to error and method to error_404
$this->check_method();
return $this->class;
}
function check_method()
{
$ignore_remap = true;
$class = $this->class;
if (class_exists($class))
{
// methods for this class
$class_methods = array_map('strtolower', get_class_methods($class));
// ignore controllers using _remap()
if($ignore_remap && in_array('_remap', $class_methods))
{
return;
}
if (! in_array(strtolower($this->method), $class_methods))
{
$this->directory = "";
$this->class = $this->error_controller;
$this->method = $this->error_method_404;
include(APPPATH.'controllers/'.$this->fetch_directory().$this->error_controller.EXT);
}
}
}
function show_404()
{
include(APPPATH.'controllers/'.$this->fetch_directory().$this->error_controller.EXT);
call_user_func(array($this->error_controller, $this->error_method_404));
}
}
/* End of file MY_Router.php */
/* Location: ./system/application/libraries/MY_Router.php */
Solved - the wordpress blog integrated in the site was setting the 404 status for all non wordpress pages i.e. codeigniter pages
index.php of CI had the following code which needed to be commented out
/*
*---------------------------------------------------------------
* WORDPRESS INTEGRATION
*---------------------------------------------------------------
* The ci_site_url function helps to avoid collision between WP & CI.
*/
//header("HTTP/1.0 200 OK");
define('WP_USE_THEMES', false);
require_once './blog/wp-blog-header.php';
add_filter('site_url', 'ci_site_url', 1);
function ci_site_url()
{
include(APPPATH.'/config/config.php');
return $config['base_url'];
}
I'm looking for a one line route to route dashed controller and method names to the actual underscored controller and method names.
For example the URL
/controller-name/method-name-which-is-long/
would route to
/controller_name/method_name_which_is_long/
see: http://codeigniter.com/forums/viewreply/696690/ which gave me the idea to ask :)
That is exactly my requirement too and I was using routes like
$route['logued/presse-access'] = "logued/presse_access";
In my previous project I needed to create 300-400 routing rules, most of them are due to dash to underscore conversion.
For my next project I eagerly want to avoid it. I have done some quick hack and tested it, though have not used in any live server, its working for me. Do the following..
Make sure the subclass_prefix is as follows in your system/application/config/config.php
$config['subclass_prefix'] = 'MY_';
Then upload a file named MY_Router.php in system/application/libraries directory.
<?php
class MY_Router extends CI_Router {
function set_class($class)
{
//$this->class = $class;
$this->class = str_replace('-', '_', $class);
//echo 'class:'.$this->class;
}
function set_method($method)
{
// $this->method = $method;
$this->method = str_replace('-', '_', $method);
}
function _validate_request($segments)
{
// Does the requested controller exist in the root folder?
if (file_exists(APPPATH.'controllers/'.str_replace('-', '_', $segments[0]).EXT))
{
return $segments;
}
// Is the controller in a sub-folder?
if (is_dir(APPPATH.'controllers/'.$segments[0]))
{
// Set the directory and remove it from the segment array
$this->set_directory($segments[0]);
$segments = array_slice($segments, 1);
if (count($segments) > 0)
{
// Does the requested controller exist in the sub-folder?
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().str_replace('-', '_', $segments[0]).EXT))
{
show_404($this->fetch_directory().$segments[0]);
}
}
else
{
$this->set_class($this->default_controller);
$this->set_method('index');
// Does the default controller exist in the sub-folder?
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT))
{
$this->directory = '';
return array();
}
}
return $segments;
}
// Can't find the requested controller...
show_404($segments[0]);
}
}
Now you can freely use url like http://example.com/logued/presse-access and it will call the proper controller and function by automatically converting dash to underscore.
Edit:
Here is our Codeigniter 2 solution which overrides the new CI_Router functions:
<?php
class MY_Router extends CI_Router {
function set_class($class)
{
$this->class = str_replace('-', '_', $class);
}
function set_method($method)
{
$this->method = str_replace('-', '_', $method);
}
function set_directory($dir) {
$this->directory = $dir.'/';
}
function _validate_request($segments)
{
if (count($segments) == 0)
{
return $segments;
}
// Does the requested controller exist in the root folder?
if (file_exists(APPPATH.'controllers/'.str_replace('-', '_', $segments[0]).'.php'))
{
return $segments;
}
// Is the controller in a sub-folder?
if (is_dir(APPPATH.'controllers/'.$segments[0]))
{
// Set the directory and remove it from the segment array
$this->set_directory($segments[0]);
$segments = array_slice($segments, 1);
while(count($segments) > 0 && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]))
{
// Set the directory and remove it from the segment array
$this->set_directory($this->directory . $segments[0]);
$segments = array_slice($segments, 1);
}
if (count($segments) > 0)
{
// Does the requested controller exist in the sub-folder?
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().str_replace('-', '_', $segments[0]).'.php'))
{
if ( ! empty($this->routes['404_override']))
{
$x = explode('/', $this->routes['404_override']);
$this->set_directory('');
$this->set_class($x[0]);
$this->set_method(isset($x[1]) ? $x[1] : 'index');
return $x;
}
else
{
show_404($this->fetch_directory().$segments[0]);
}
}
}
else
{
// Is the method being specified in the route?
if (strpos($this->default_controller, '/') !== FALSE)
{
$x = explode('/', $this->default_controller);
$this->set_class($x[0]);
$this->set_method($x[1]);
}
else
{
$this->set_class($this->default_controller);
$this->set_method('index');
}
// Does the default controller exist in the sub-folder?
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.'.php'))
{
$this->directory = '';
return array();
}
}
return $segments;
}
// If we've gotten this far it means that the URI does not correlate to a valid
// controller class. We will now see if there is an override
if ( ! empty($this->routes['404_override']))
{
$x = explode('/', $this->routes['404_override']);
$this->set_class($x[0]);
$this->set_method(isset($x[1]) ? $x[1] : 'index');
return $x;
}
// Nothing else to do at this point but show a 404
show_404($segments[0]);
}
}
Now one has to place this file like application/core/MY_Router.php and make sure he has subclass_prefix is defined as $config['subclass_prefix'] = 'MY_'; in application/config/config.php
Few extra lines of code has been added in method _validate_request():
while(count($segments) > 0 && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]))
{
// Set the directory and remove it from the segment array
$this->set_directory($this->directory . $segments[0]);
$segments = array_slice($segments, 1);
}
It is used so that one can make use of multi-level subdirectory inside controllers directory, whereas normally we can use single level subdirectory inside controllers folder and can call it in url. One can remove this code if it not necessary but it has no harm on the normal flow.
Just coming back to this question after upgrading to CodeIgniter 2. Here is a solution which is more robust than the accepted answer because it will survive CodeIgniter core updates.
<?php
class MY_Router extends CI_Router
{
public function set_class($class)
{
parent::set_class($this->_repl($class));
}
public function set_method($method)
{
parent::set_method($this->_repl($method));
}
public function _validate_request($segments)
{
if (isset($segments[0]))
$segments[0] = $this->_repl($segments[0]);
if (isset($segments[1]))
$segments[1] = $this->_repl($segments[1]);
return parent::_validate_request($segments);
}
private function _repl($s)
{
return str_replace('-', '_', $s);
}
}
It should be saved as application/core/MY_Router.php. Now, you can have Controller and Method names with underscores in them such as Abc_Def (in file abc_def.php) and refer to them with the URL /abc-def.
Actually this is native now in Codeigniter 3
Just do this in routes file
$route['translate_uri_dashes'] = TRUE;
And it will do it for controllers and methods automatically .
Please vote this answer up , as it's most recent
<?php
class MY_Router extends CI_Router
{
function _set_request($segments = array()) {
parent::_set_request(str_replace('-', '_', $segments));
}
}
?>
Put this file MY_Router.php inside /application/libraries (CI1) or /application/core (CI2)
Remember that this will effect all segments, not only module, controller and method.
Alternative to this extend is to add each segment to router.php
$route['this-is-a-module-or-controler'] = 'this_is_a_module_or_controller';
As you can see the extend method would be easier to use. You can choose to make the function also to handle only the first two or three segments so that the other segments are not affected with the _ replacement.
This is an old question, but I'd like to post that e-mike had a great solution to this problem, and a lot simpler.
<?php
public function _set_request($segments){
// Fix only the first 2 segments
for($i = 0; $i < 2; ++$i){
if(isset($segments[$i])){
$segments[$i] = str_replace('-', '_', $segments[$i]);
}
}
// Run the original _set_request method now, giving it our newly replaced segments
parent::_set_request($segments);
}
?>
Hope this helps anyone else with this problem.
I believe what you are looking for is a either a pre-system or else a pre-controller hook that will take the requested URI and update it.
Overriding the Router class is a nice approach, there is also a way of replacing - with _ by registering a "pre-system" hook.
First create the hook by adding these lines to your ‘config/hooks.php’ file:
$hook['pre_system'] = array(
'class' => '',
'function' => 'prettyurls',
'filename' => 'myhooks.php',
'filepath' => 'hooks',
'params' => array()
);
Now create a ‘myhooks.php’ file within the ‘application/hooks’ folder and add this function (don’t forget to open a PHP tag if this is the first hook):
<?php
function prettyurls() {
if (is_array($_GET) && count($_GET) == 1 && trim(key($_GET), '/') != '') {
$newkey = str_replace('-','_',key($_GET));
$_GET[$newkey] = $_GET[key($_GET)];
unset($_GET[key($_GET)]);
}
if (isset($_SERVER['PATH_INFO'])) $_SERVER['PATH_INFO'] = str_replace('-','_',$_SERVER['PATH_INFO']);
if (isset($_SERVER['QUERY_STRING'])) $_SERVER['QUERY_STRING'] = str_replace('-','_',$_SERVER['QUERY_STRING']);
if (isset($_SERVER['ORIG_PATH_INFO'])) $_SERVER['ORIG_PATH_INFO'] = str_replace('-','_',$_SERVER['ORIG_PATH_INFO']);
if (isset($_SERVER['REQUEST_URI'])) $_SERVER['REQUEST_URI'] = str_replace('-','_',$_SERVER['REQUEST_URI']);
}
You will probably need to edit your ‘config/config.php’ file to enable hooks (around line 91 for me):
$config['enable_hooks'] = TRUE;
This answer is ripped from http://codeigniter.com/forums/viewthread/124396/#644012
I'm not sure if you could do that with a route...
However, somewhere in the Codeigniter core libraries (possibly Router or URI) will be something that converts the underscored uris into a camelcase class name.
I had a quick look and couldn't find it, but if you do, just copy that library to your application/libraries folder, and modify it there.
You can use this _remap() method to handle such behavior. Place this method in your controllers or create a core controller and place it in.
public function _remap($method, $params = array()){
if(method_exists($this, $method)){
return call_user_func_array(array($this, $method), $params);
}else{
$method = str_replace("-", "_", $method);
if(method_exists($this, $method)){
return call_user_func_array(array($this, $method), $params);
}
}
show_404();
}