Manage URL routes in own php framework - php

I'm creating a PHP Framework and I have some doubts...
The framework takes the url in this way:
http:/web.com/site/index
It takes the first parameter to load controller (site) and then loads the specific action (index).
If you've installed the framework in a base URL works ok, but if you install it in a subfolder like this:
http://web.com/mysubfolder/controller/action
My script parses it as controller = mysubfolder and action = controller.
If you have more subfolders the results will be worst.
This is my Route code:
Class Route
{
private $_htaccess = TRUE;
private $_suffix = ".jsp";
public function params()
{
$url='';
//nombre del directorio actual del script ejecutandose.
//basename(dirname($_SERVER['SCRIPT_FILENAME']));
if($this->_htaccess !== FALSE):
//no está funcionando bien si está en un subdirectorio web, por ej stynat.dyndns.org/subdir/
// muestra el "subdir" como primer parámetro
$url = $_SERVER['REQUEST_URI'];
if(isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])):
$url = str_replace("?" . $_SERVER['QUERY_STRING'], '',$url);
endif;
else:
if(isset($_SERVER['PATH_INFO'])):
$url = $_SERVER['PATH_INFO'];
endif;
endif;
$url = explode('/',preg_replace('/^(\/)/','',$url));
var_dump($url);
var_dump($_GET);
}
}
Thanks for any help you can give.

You are missing a base path. The routing script must now where to start when detecting a pattern or route detected.
Pseudo code:
//set the base URI
$base_uri = '/base';
//remove the base URI from the original uri. You can also REGEX or string match against it if you want
$route_uri = str_replace($base_uri,'',$uri);
//perform route matching $route_uri, in your code a simple explode
$url = explode('/',preg_replace('/^(\/)/','',$route_uri));
You can use this with or without RewriteBase for your .htaccess so long as they use the same harness - index.php.
Additionally, you can improve your route match procedure using Regular Expressions function like preg_match and preg_match_all. They let you define a pattern to match against and results to an array of matching strings - see http://php.net/manual/en/function.preg-match.php.

Even if you are creating your own framework, there is no reason not to reuse robust, well tested and documented components, like this Routing component.
Just use Composer, which has become the standard for dependency management in PHP, and you'll be fine. Add as many components as you want to your stack.
And here you have a must read guide on how to make your own framework.

Yes, I think I know how to fix that.
(Disclaimer: I know that you know most of this but I am going to explain everything for others who may not know some of the gotchas)
Using PATH_INFO and .htaccess
There is a trick in php where if you go to a path like:
http://web.com/mysubfolder/index.php/controller/action
you will get "/controller/action" in the $_SERVER['PATH_INFO'] variable
Now what you need to do is take a .htaccess file (or equivalent) and make it tell your php script the current folder depth.
To do this, put the .htaccess file into the "mysubfolder"
mysubfolder
.htaccess
index.php
.htaccess should contain:
RewriteEngine on
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# otherwise forward it to index.php
RewriteRule (.*) index.php/$1
(I used the yii framework manual as reference, I also recommend using the html5 boilerplate)
Basically you set it up to redirect everything to index.php at a specific point in the url.
Now if you visit: http://web.com/mysubfolder/index.php/controller/action
Now you can get the right path "/controller/action" from $_SERVER['PATH_INFO']
Except it's not going to have any value if you visit http://web.com/mysubfolder/ because the .htaccess file will ignore the rewrite because the http://web.com/mysubfolder/ path requests the mysubfolder/index.php which actually exists and gets denied thank yo RewriteCond %{REQUEST_FILENAME} !-f.
ifsetor function
For this you can use this super handy function called ifsetor (I don't remember where I got it)
function ifsetor(&$variable, $default = null) {
return isset($variable)? $variable: $default;
}
What it does is take a reference to a variable that might or might not exist and provide a default if it does not exist without throwing any notices or errors
Now you can use it to take the PATH_INFO variable safely like so
In your index.php
function ifsetor(&$variable, $default = null) {
return isset($variable)? $variable: $default;
}
$path = ifsetor($_SERVER['PATH_INFO'],'/');
var_dump($path);
php 5.4 also has this new shorter ternary format which you can use if you don't care about notices (I do)
$path = $_SERVER['PATH_INFO']?:'/';
Handling the QUERY_STRING
Now tecnically you are not getting a URL, it is merely its path part and will not contain the query_string, for example when visiting
http://web.com/mysubfolder/index.php/test?param=val
you will only get '/test' in the $path variable, to get the query string use the $_SERVER['QUERY_STRING'] variable
index.php
function ifsetor(&$variable, $default = null) {
return isset($variable)? $variable: $default;
}
$path = ifsetor($_SERVER['PATH_INFO'],'/');
$fullpath = ($_SERVER['QUERY_STRING'])? $path.'?'.$_SERVER['QUERY_STRING']:$path;
var_dump($fullpath);
But that might depend on your needs
$_SERVER['QUERY_STRING'] vs $_GET
Also keep in mind that the $_SERVER['QUERY_STRING'] variable is different from the $_GET and $_REQUEST variables because it keeps duplicate parameters from the query string, for example:
Visiting this page
http://web.com/mysubfolder/controller/action?foo=1&foo=2&foo=3
if going to give you a $_SERVER['QUERY_STRING'] that looks like
?foo=1&foo=2&foo=3
While the $_GET variable is going to be an array like this:
array(
'foo'=>'3'
);
If you don't have .htaccess
You can try using the SCRIPT_NAME to your advantage
list($url) = explode('?',$_SERVER['REQUEST_URI']);
list($basepath) = explode('index.php',$_SERVER['SCRIPT_NAME']);
$url = substr($url,strlen($basepath));
If you like to blow up stuff like me :)
Your case
Class Route
{
private $_htaccess = TRUE;
private $_suffix = ".jsp";
public function params()
{
$url='';
//nombre del directorio actual del script ejecutandose.
//basename(dirname($_SERVER['SCRIPT_FILENAME']));
if($this->_htaccess !== FALSE):
//no está funcionando bien si está en un subdirectorio web, por ej stynat.dyndns.org/subdir/
// muestra el "subdir" como primer parámetro
list($url) = explode('?',$_SERVER['REQUEST_URI']);
$basepath = dirname($_SERVER['SCRIPT_NAME']);
$basepath = ($basepath==='/')? $basepath: $basepath.'/';
$url = substr($url,strlen($basepath));
else:
if(isset($_SERVER['PATH_INFO'])):
$url = $_SERVER['PATH_INFO'];
$url = preg_replace('|^/|','',$url);
endif;
endif;
$url = explode('/',$url);
var_dump($url);
var_dump($_GET);
}
}
I hope this helps
P.S. Sorry for the late reply :(

At some point you will have to check the $_SERVER ['HTTP_HOST'] and a config var defined by the programmer/user wich indicates the subfolder(s) where the app is located, and remove the portion you are not interested in from the rest of the URL.
You can check this forum post on the codeigniter formus for some hints.
CodeIgniter uses another different way to route the controller/method internally.
You do the routing by the $_SERVER['PATH_INFO'] value. You use the urls like this: myapp.com/index.php/controller/method .
To avoid showing index.php on the uri you must rely on an Apache rewrite rule, but even with that I think that the CI one is a nice solution, once you have your index file location, you can avoid all the hassle of parsing the URL.

If I am understanding what you are after correctly, then one solution may be to carry on doing what you are doing, but also get the path of the main routing script (using realpath() for example).
If the last folder (or folder before that etc) matches the beginning URL item (or another section), you ignore it and go for the next one.
Just my 2 cents :-).

Within the application configuration script place a variable which will be set to the path the application runs at.
An alternative is to dynamically set that path.
Before the part
$url = explode('/',preg_replace('/^(\/)/','',$url));
strip the location (sub-folder) path out of the $url string using the predefined application path.

This is how i implemented loader.php
<?php
/*#author arun ak
auto load controller class and function from url*/
class loader
{
private $request;
private $className;
private $funcName;
function __construct($folder = array())
{
$parse_res = parse_url($this->createUrl());
if(!empty($folder) && trim($folder['path'],DIRECTORY_SEPARATOR)!='')
{
$temp_path = explode(DIRECTORY_SEPARATOR,trim($parse_res['path'],DIRECTORY_SEPARATOR));
$folder_path = explode(DIRECTORY_SEPARATOR,trim($folder['path'],DIRECTORY_SEPARATOR));
$temp_path = array_diff($temp_path,$folder_path);
if(empty($temp_path))
{
$temp_path = '';
}
}else
{
if(trim($parse_res['path'],DIRECTORY_SEPARATOR)!='')
{
$temp_path = explode(DIRECTORY_SEPARATOR,trim($parse_res['path'],DIRECTORY_SEPARATOR));
}
else
$temp_path ='';
}
if(is_array($temp_path))
{
if(count($temp_path) ==1)
{
array_push($temp_path,'index');
}
foreach($temp_path as $pathname)
{
$this->request .= $pathname.':';
}
}
else $this->request = 'index'.':'.'index';
}
private function createUrl()
{
$pageURL = (#$_SERVER["HTTPS"] == "on") ? "https://" : "http://";
$pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
return $pageURL;
}
public function autolLoad()
{
if($this->request)
{
$parsedPath = explode(':',rtrim($this->request,':'));
if(is_file(APPLICATION_PATH.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.$parsedPath[0].'_controller'.'.php'))
{
include_once(APPLICATION_PATH.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.$parsedPath[0].'_controller'.'.php');
if(class_exists($parsedPath[0].'_controller'))
{
$class = $parsedPath[0].'_controller';
$obj = new $class();
//$config = new config('localhost','Winkstore','nCdyQyEdqDbBFpay','mawinkcms');
//$connect = connectdb::getinstance();
//$connect->setConfig($config);
//$connection_obj = $connect->connect();
//$db = $connect->getconnection();//mysql link object
//$obj->setDb($db);
$method = $parsedPath[1];
if(method_exists ($obj ,$parsedPath[1] ))
{
$obj->$method();
}else die('class method '.$method.' not defined');
}else die('class '.$parsedPath[0]. ' has not been defined' );
} else die('controller not found plz define one b4 u proceed'.APPLICATION_PATH.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.$parsedPath[0].'.php');
}else{ die('oops request not set,i know tis is not the correct way to error :)'); }
}
}
Now in my index file
//include_once('config.php');
include_once('connectdb.php');
require_once('../../../includes/db_connect.php');
include_once('view.php');
include_once('abstractController.php');
include_once('controller.php');
include_once('loader.php');
$loader = new loader(array('path'=>DIRECTORY_SEPARATOR.'magsonwink'.DIRECTORY_SEPARATOR.'modules'.DIRECTORY_SEPARATOR.'admin'.DIRECTORY_SEPARATOR.'atom'.DIRECTORY_SEPARATOR));
$loader->autolLoad();
I haven't used the concept of modules.only controller action and view are rendered.

Are you sure you have your htaccess correctly?
I guess if you're placing your framework on subfolder, then you have to change your RewriteBase in htaccess file from / to /subfolder/. it would be something like this:
# on root
RewriteBase /
#on subfolder
RewriteBase /subfolder/
that's only thing I could wonder of that in your case ...

I don't use OOP, but could show you some snippets of how I do things to dynamically detect if I'm in a subdirectory. Too keep it short and to the point I'll only describe parts of it instead of posting all the code.
So I start out with a .htaccess that send every request to redirect.php in which I splice up the $_SERVER['REQUEST_URI'] variable. I use regex to decide what parts should be keys and what should be values (I do this by assigning anything beginning with 0-9 as a value, and anything beginning with a-z as key) and build the array $GET[].
I then check the path to redirect.php and compare that to my $GET array, to decide where the actual URL begins - or in your case, which key is the controller. Would look something like this for you:
$controller = keyname($GET, count(explode('/', dirname($_SERVER['SCRIPT_NAME']))));
And that's it, I have the first part of the URL. The keyname() function looks like this:
/*************************************
* get key name from array position
*************************************/
function keyname ($arr, $pos)
{
$pos--;
if ( ($pos < 0) || ( $pos >= count($arr) ) )
return ""; // set this any way you like
reset($arr);
for($i = 0;$i < $pos; $i++) next($arr);
return key($arr);
}
To get the links pointing right I use a function called fixpath() like this:
print 'link';
And this is how that function looks:
/*************************************
* relative to absolute path
*************************************/
function fixpath ($path)
{
$root = dirname($_SERVER['SCRIPT_NAME']);
if ($root == "\\" || $root == ".") {
$root = "";
}
$newpath = explode('/', $path);
$newpath[0] .= $root;
return implode('/', $newpath);
}
I hope that helps and can give you some inspiration.

Forget about "reinventing the wheel is wrong" claims. They don't have to use our wheels. I walked on the same road a while ago and i'm totally grateful what i get... i hope you will too
When it comes to the answer to your specific problem -which if faced too- there is a very easy solution. it's a new line in .htaccess at root folder...
Just add line below to your root .htaccess file ; (if your subfoler is "subfolder" )
RewriteRule subfolder/ - [L]
This will leave apart this folder from rewriting directives
By using this way you can install as many instances of your framework as you wish. But if you want this to be framework driven then you have to create/change .htaccess within your framework.

Create /myBaseDirectory/public directory and put your files there - like index.php.
This works because Apache sees this directory like base directory.

basically grab the url string after your first slash, and then explode it into an array (i use '/' as a delimiter).
then carefully array_shift off your elements and store them as variables
item 0: controller
item 1: the action / method in that controller
item 2 thru n: the remaining array is your params

Related

Friendly URL's with an IndexController

My current router / FrontController is setup to dissect URL's in the format:
http://localhost/controller/method/arg1/arg2/etc...
However, I'm not sure how to get certain requests to default to the IndexController so that I can type:
http://localhost/contact
or
http://localhost/about/portfolio
Instead of:
http://localhost/index/contact
or
http://localhost/index/about/portfolio
How is this accomplished?
<?php
namespace framework;
class FrontController {
const DEFAULT_CONTROLLER = 'framework\controllers\IndexController';
const DEFAULT_METHOD = 'index';
public $controller = self::DEFAULT_CONTROLLER;
public $method = self::DEFAULT_METHOD;
public $params = array();
public $model;
public $view;
function __construct() {
$this->model = new ModelFactory();
$this->view = new View();
}
// route request to the appropriate controller
public function route() {
// get request path
$basePath = trim(substr(PUBLIC_PATH, strlen($_SERVER['DOCUMENT_ROOT'])), '/') . '/';
$path = trim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), '/');
if($basePath != '/' && strpos($path, $basePath) === 0) {
$path = substr($path, strlen($basePath));
}
// determine what action to take
#list($controller, $method, $params) = explode('/', $path, 3);
if(isset($controller, $method)) {
$obj = __NAMESPACE__ . '\\controllers\\' . ucfirst(strtolower($controller)) . 'Controller';
$interface = __NAMESPACE__ . '\\controllers\\' . 'InterfaceController';
// make sure a properly implemented controller and corresponding method exists
if(class_exists($obj) && method_exists($obj, $method) && in_array($interface, class_implements($obj))) {
$this->controller = $obj;
$this->method = $method;
if(isset($params)) {
$this->params = explode('/', $params);
}
}
}
// make sure we have the appropriate number of arguments
$args = new \ReflectionMethod($this->controller, $this->method);
$totalArgs = count($this->params);
if($totalArgs >= $args->getNumberOfRequiredParameters() && $totalArgs <= $args->getNumberOfParameters()) {
call_user_func_array(array(new $this->controller, $this->method), $this->params);
} else {
$this->view->load('404');
}
}
}
You can use your URLs by one of two methods:
Establish the controllers the way your routing defines them
example.com/contact => Have a "contact" controller with default or index action
example.com/about/portfolio => Have an "about" controller with a "portfolio" action
Because your currently available routing says your URL is treated like "/controller/method", there is no other way.
Establish dynamic routing to allow multiple URLs to be handled by a single controller
Obviously this needs a bit of configuration because one cannot know which URLs are valid and which one should be redirected to the generic controller, and which ones should not. This is somehow a replacement for any of the rewriting or redirecting solutions, but as it is handled on the PHP level, change might be easier to handle (some webserver configurations do not offer .htaccess because of performance reasons, and it generally is more effort to create these).
Your configuration input is:
The URL you want to be handled and
The controller you want the URL passed to, and it's action.
You'll end up having an array structure like this:
$specialRoutes = array(
"/contact" => "IndexController::indexAction",
"/about/portfolio" => "IndexController::indexAction"
);
What's missing is that this action should get the current URL passed as a parameter, or that the path parts become designated parameters within your URL schema.
All in all this approach is a lot harder to code. To get an idea, try to look at the routing of common MVC frameworks, like Symfony and Zend Framework. They offer highly configurable routing, and because of this, the routing takes place in multiple classes. The main router only reads the configuration and then passes the routing of any URL to the configured routers if a match is detected.
Based on your code snippet I'd do it like this (pseudo php code):
$handler = get_controller($controller);
if(!$handler && ($alias = lookup_alias($path))) {
list($handler, $method) = $alias;
}
if(!$handler) error_404();
function lookup_alias($path) {
foreach(ALL_CONTROLLERS as $controller) {
if(($alias = $controller->get_alias($path))) {
return $alias;
}
}
return null;
}
So basically in case there is no controller to handle a certain location you check if any controller is configured to handle the given path as an alias and if yes return that controller and the method it maps to.
You can create a rewrite in your webserver for these exceptions. For example:
RewriteRule ^contact$ /index/contact
RewriteRule ^about/portfolio$ /about/portfolio
This will allow you to have simplified URLs that map to your regular structure.
You could have a dynamic rule if you are able to precisely define what should be rewritten to /index. For example:
RewriteRule ^([a-z]+)$ /index/$1
Try this dynamic htaccess rewrite rule:
RewriteRule ^(.+)/?$ /index/$1 [QSA]
The QSA flag in the above rule allows you to also add a query string to the end if you want, like this:
http://localhost/contact?arg1=1&arg2=2
EDIT: This rule would also handle cases such as /about/portfolio:
RewriteRule ^(.+)/?(.+)?$ /index/$1 [QSA]

Stop clean URLs from affecting relative paths in page HTML

I have implemented clean URLs using the following in my .htaccess
RewriteEngine on
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]
And in my index.php the $_SERVER['PATH_INFO'] is parsed to determine the requested page and any parameters.
This all works fine, however any requests for a url with more than one segment, i.e.
www.example.com/page/foo/bar
cause all linked assets (css, js, images) and hyperlinks on the page to break because they use relative paths. A absolute path with an implied domain (aka prepending all paths with "/") is not viable because the site has to be accessible and working properly as a subdirectory of a parent website in addition to its own root URL. I can not hardcode the fully qualified absolute path for all links either, as I do not have control over some of the content that will be on the page. Using the <base> tag is also not an possibility.
Do I have any other options here?
Does every request to PHP fall on the same index.php file? In that case, the file itself knows where it is, and can calculate what directory it's in.
This isn't the neatest way to do it, but it might give you some ideas to work from.
define('DR', $_SERVER['DOCUMENT_ROOT');
define('BD', str_replace(DR, '', dirname($_SERVER['SCRIPT_FILENAME'] . '/'); // base dir
And then prepend all links with BD.
Do what you normally do when you create a php router... create a link generation facility for assets and URLs.
Since youre using a front controller you can define all your directories up front based on configuration and then generate the paths to things like:
function asset_url($filename, $type = 'img') {
global $config;
if(isset($config['paths'][$type])) {
return $config['paths'][$type] . '/' . $filename;
}
}
Normally id register this configuration with the Registry pattern implemntation instead of using global but i dunno your set up, so this is just a simple example.
It's a tricky solution. The easiest thing to do is introduce a baseUrl parameter to your application. It would be empty if the site is running at the root directory, and would be something like /corp/site is running out of domain.com/corp/site.
This way, when you display URLs, you would use <?php echo $app_base_url . '/css/file.css' ?>
I use Zend Framework a lot and they have a class called Zend_Controller_Request_Http which has a method called getBaseUrl that goes through some complex logic examining the various $_SERVER variables about the script path and the request filename which ultimately figures out what your base Url actually is.
If you use that specific piece of Zend Framework, you can do something like this:
$request = new Zend_Controller_Request_Http();
$baseUrl = $request->getBaseUrl();
Since Zend Framework is modular, you don't need to include the whole library, I've used just this class before (and its dependencies) so I could easily calculate the base URL in a small application without needing the whole ZF library.
To give you an idea of what logic is involved in determining this, here is the code that does most of the work (note, don't try to use this directly, instead use the Zend Framework classes as shown above).
/**
* Set the base URL of the request; i.e., the segment leading to the script name
*
* E.g.:
* - /admin
* - /myapp
* - /subdir/index.php
*
* Do not use the full URI when providing the base. The following are
* examples of what not to use:
* - http://example.com/admin (should be just /admin)
* - http://example.com/subdir/index.php (should be just /subdir/index.php)
*
* If no $baseUrl is provided, attempts to determine the base URL from the
* environment, using SCRIPT_FILENAME, SCRIPT_NAME, PHP_SELF, and
* ORIG_SCRIPT_NAME in its determination.
*
* #param mixed $baseUrl
* #return Zend_Controller_Request_Http
*/
public function setBaseUrl($baseUrl = null)
{
if ((null !== $baseUrl) && !is_string($baseUrl)) {
return $this;
}
if ($baseUrl === null) {
$filename = (isset($_SERVER['SCRIPT_FILENAME'])) ? basename($_SERVER['SCRIPT_FILENAME']) : '';
if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) {
$baseUrl = $_SERVER['SCRIPT_NAME'];
} elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $filename) {
$baseUrl = $_SERVER['PHP_SELF'];
} elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) {
$baseUrl = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility
} else {
// Backtrack up the script_filename to find the portion matching
// php_self
$path = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '';
$file = isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '';
$segs = explode('/', trim($file, '/'));
$segs = array_reverse($segs);
$index = 0;
$last = count($segs);
$baseUrl = '';
do {
$seg = $segs[$index];
$baseUrl = '/' . $seg . $baseUrl;
++$index;
} while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos));
}
// Does the baseUrl have anything in common with the request_uri?
$requestUri = $this->getRequestUri();
if (0 === strpos($requestUri, $baseUrl)) {
// full $baseUrl matches
$this->_baseUrl = $baseUrl;
return $this;
}
if (0 === strpos($requestUri, dirname($baseUrl))) {
// directory portion of $baseUrl matches
$this->_baseUrl = rtrim(dirname($baseUrl), '/');
return $this;
}
$truncatedRequestUri = $requestUri;
if (($pos = strpos($requestUri, '?')) !== false) {
$truncatedRequestUri = substr($requestUri, 0, $pos);
}
$basename = basename($baseUrl);
if (empty($basename) || !strpos($truncatedRequestUri, $basename)) {
// no match whatsoever; set it blank
$this->_baseUrl = '';
return $this;
}
// If using mod_rewrite or ISAPI_Rewrite strip the script filename
// out of baseUrl. $pos !== 0 makes sure it is not matching a value
// from PATH_INFO or QUERY_STRING
if ((strlen($requestUri) >= strlen($baseUrl))
&& ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0)))
{
$baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
}
}
$this->_baseUrl = rtrim($baseUrl, '/');
return $this;
}

Codeigniter Dynamic Routing

Hi i wont to make something like that.
http:// example.com/ - Main Controller
http:// example.com/rules/ - Main Controller where content get from database, but if not exist
return 404 page. (It's ok, isn't problem.)
But if i have subfolder in application/controlles/rules/
I want to redirect it to Main Contorller at Rules folder.
This follow code can solve problem, but i don't know how it right realise.
At routes.php:
$route['default_controller'] = "main";
$route['404_override'] = '';
$dirtest = $route['(:any)'];
if (is_dir(APPPATH.'controllers/'.$dirtest)) {
$route['(:any)'] = $dirtest.'/$1';
} else {
$route['(:any)'] = 'main/index/$1';
}
Ok, what I have:
controllers/main.php
class Main extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('main_model');
}
public function index($method = null)
{
if (is_dir(APPPATH.'controllers/'.$method)) {
// Need re-rout to the application/controllers/$method/
} else {
if ($query = $this->main_model->get_content($method)) {
$data['content'] = $query[0]->text;
// it shows at views/main.php
} else {
show_404($method);
}
}
$data['main_content'] = 'main';
$this->load->view('includes/template', $data);
}
}
Updated Again (routes.php):
So, seem's like what i search (work example):
$route['default_controller'] = "main";
$route['404_override'] = '';
$subfolders = glob(APPPATH.'controllers/*', GLOB_ONLYDIR);
foreach ($subfolders as $folder) {
$folder = preg_replace('/application\/controllers\//', '', $folder);
$route[$folder] = $folder.'/main/index/';
$route[$folder.'/(:any)'] = $folder.'/main/$1';
}
$route['(:any)'] = 'main/index/$1';
But, in perfectly need some like this:
http:// example.com/1/2/3/4/5/6/...
Folder "controllers" has subfolder "1"?
YES: Folder "1" has subfolder "2"?
NO: Folder "1" has controller "2.php"?
NO: Controller "controllers/1/main.php" has function "2"?
YES: redirect to http:// example.com/1/2/ - where 3,4,5 - parameters..
It is realy nice, when you have structure like:
http://example.com/blog/ - recent blog's posts
http://example.com/blog/2007/ - recent from 2007 year blog's posts
http://example.com/blog/2007/06/ - same with month number
http://example.com/blog/2007/06/29/ - same with day number
http://example.com/blog/web-design/ - recent blog's post's about web design
http://example.com/blog/web-design/2007/ - blog' posts about web design from 2007 years.
http://example.com/blog/current-post-title/ - current post
Same interesting i find http://codeigniter.com/forums/viewthread/97024/#490613
I didn't thoroughly read your question, but this immediately caught my attention:
if (is_dir($path . '/' . $folder)) {
echo "$route['$folder/(:any)'] = '$folder/index/$1';"; //<---- why echo ???
}
Honestly I'm not sure why this didn't cause serious issues for you in addition to not working.
You don't want to echo the route here, that will just try to print the string to screen, it's not even interpreted as PHP this way. There are also some issues with quotes that need to be remedied so the variables can be read as variables, not strings. Try this instead:
if (is_dir($path . '/' . $folder)) {
$route[$folder.'/(:any)'] = $folder.'/index/$1';
}
Aside: I'd like to offer some additional resources that are not directly related to your problem, but should help you nonetheless with a solution:
Preferred way to remap calls to controllers: http://codeigniter.com/user_guide/general/controllers.html#remapping
Easier way to scan directories: http://php.net/manual/en/function.glob.php
It's hard to say why the registering of your routes fails. From your code I can see that you're not registering the routes (you just echo them), additionally I see that the usage of variables in strings are used inconsistently. Probably you mixed this a bit, the codeigniter documentation about routes is not precise on it either (in some minor points, their code examples are not really syntactically correct, but overall it's good).
I suggest you first move the logic to register your dynamic routes into a function of it's own. This will keep things a bit more modular and you can more easily change things and you don't pollute the global namespace with variables.
Based on this, I've refactored your code a bit. It does not mean that this works (not tested), however it might make things more clear when you read it:
function register_dynamic_routes($path, array &$route)
{
$nodes = scandir($path);
if (false === $nodes)
{
throw new InvalidArgumentException(sprintf('Path parameter invalid. scandir("$path") failed.', $path));
}
foreach ($nodes as $node)
{
if ($node === '.' or $node === '..')
continue
;
if (!is_dir("{$path}/{$node}")
continue
;
$routeDef = "{$folder}/(:any)";
$routeResolve = "{$folder}/index/\$1";
$route[$routeDef] = $routeResolve;
# FIXME debug output
echo "\$route['{$routeDef}'] = '{$routeResolve}';";
}
}
$path = APPPATH.'controllers/';
register_dynamic_routes($path, $route);
$route['(:any)'] = 'main/index/$1';
Next to this you probably might not want to shift everything onto the index action, but a dynamic action instead. Furthermore, you might want to have a base controller that is delegating everything into the sub-controllers instead of adding the routes per controller. But that's up to you. The example above is based on the directory approach you outlined in your question.
Edit: Additional information is available in the Controllers section next to the URI Routing section
All this seems kind of complicated.
Plus, if you have hundreds (or thousands or more?) of possible routes in a database you may not want to load all of them into the "$routes" array every time any page loads in your application.
Instead, why not just do this?
last line of routes.php:
$route['404_override'] = 'vanity';
Controller file: Vanity.php:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Vanity extends MY_Controller {
/**
* Vanity Page controller.
*
*/
public function __construct() {
parent::__construct();
}
public function index()
{
$url = $_SERVER['PHP_SELF'];
$url = str_replace("/index.php/", "", $url);
// you could check here if $url is valid. If not, then load 404 via:
//
// show_404();
//
// or, if it is valid then load the appropriate view, redirect, or
// whatever else it is you needed to do!
echo "Hello from page " . $url;
exit;
}
}
?>

codeigniter url suffix .html + hashtag

I want to build an url with hashtag on codeigniter. Example:
"http://www.example.com/blog/post/title.html#comments"
The url_config in my config.php is this:
$config['url_suffix'] = ".html";
And I used the following code to build the anchor:
anchor('blog/post/'.url_title($post->title, 'dash', TRUE).'#comments', 'comments', 'title="'.$post->title.'"');
If you know any solution please let me know it. Thanks
How about this?
anchor(site_url('blog/post/'.$post->title)."#comments");
It returns an url like this: http://example.org/blog/post/stackoverflowRocks.html#comments
If you want to use hash tags without having to pass 'site_url()' to the anchor method you can extend the CodeIgniter Config library class fairly easily.
The CodeIgniter Config library class has a method called site_url that runs when you use the anchor method. site_url, by default, adds the url_suffix after any uri you pass to it without any care or knowledge of hash tags. Fortunately, you can simply extend the Config library class to modify site_url to check for hash tags and add them to the end of the URI after the url_suffix is added.
If you feel so compelled, copy the code below and save it under '/system/application/libraries/MY_Config.php'. You may have to open up '/system/application/config/autoload.php' and add 'My_Config.php' to the autoload library array.
<?php
class MY_Config extends CI_Config {
function site_url($uri = '')
{
if (is_array($uri))
{
$uri = implode('/', $uri);
}
if ($uri == '')
{
return $this->slash_item('base_url').$this->item('index_page');
}
else
{
$suffix = ($this->item('url_suffix') == FALSE) ? '' : $this->item('url_suffix');
$hash = '';
if(substr_count($uri,'#') == 1)
{
list($uri,$hash) = explode('#',$uri);
$hash = '#'.$hash;
}
return $this->slash_item('base_url').$this->slash_item('index_page').trim($uri, '/').$suffix.$hash;
}
}
}
?>
The new site_url method sets $hash to an empty string. If a hash tag is found in the link you pass in, the link is split into an array and passed into variables. site_url will now return the link with the hash tag appended at the end (if hash code is present) after the url_suffix.

CodeIgniter PHP Framework - Need to get query string

I'm creating an e-commerce site using CodeIgniter.
How should I get the query string?
I am using a Saferpay payment gateway. The gateway response will be like this:
http://www.test.com/registration/success/?DATA=<IDP+MSGTYPE%3D"PayConfirm"+KEYID%3D"1-0"+ID%3D"KI2WSWAn5UG3vAQv80AdAbpplvnb"+TOKEN%3D"(unused)"+VTVERIFY%3D"(obsolete)"+IP%3D" 123.25.37.43"+IPCOUNTRY%3D"IN"+AMOUNT%3D"832200"+CURRENCY%3D"CHF"+PROVIDERID%3D"90"+PROVIDERNAME%3D"Saferpay+Test+Card"+ACCOUNTID%3D"99867-94913159"+ECI%3D"2"+CCCOUNTRY%3D"XX"%2F>&SIGNATURE=bc8e253e2a8c9ee0271fc45daca05eecc43139be6e7d486f0d6f68a356865457a3afad86102a4d49cf2f6a33a8fc6513812e9bff23371432feace0580f55046c
To handle the response I need to get the query string data.
Sorry, I haven't explained the problem clearly. I am getting a 'Page not found' error while getting the response from the payment site after payment.
I have tried enabling with uri_protocol = 'PATH_INFO' and enable_query_strings = 'TRUE' in config.php. While googling I found this won't work if I use htaccess rewrite.
I have already tried changing the config entries, but it doesn't work.
You can get it like this:
$this->input->get('some_variable', TRUE);
See this for more info.
I have been using CodeIgniter for over a year now. For the most part I really like it (I contribute to the forum and use it in every instance that I can) but I HATE the ARROGANCE of that statement in the manual:
Destroys the global GET array. Since
CodeIgniter does not utilize GET
strings, there is no reason to allow
it.
The presumption that you will never need GET in a CodeIgniter application is asinine! Already in just a few days, I've had to deal with post back pages from PayPal and ClickBank (I'm sure there are a million others.) Guess what, they use GET!!!
There are ways to stop this GET squashing, but they are things that tend to screw other things up. What you don't want to hear is that you have to recode all your views because you enabled querystrings and now your links are broken! Read the manual carefully on that option!
One that I like (but didn't work because setting REQUEST_URI in config.php broke my site) is extending the Input class:
class MY_Input extends CI_Input
{
function _sanitize_globals()
{
$this->allow_get_array = TRUE;
parent::_sanitize_globals();
}
}
But the best no-nonsense way is to test with print_r($_SERVER) at the URL where you need the GET variables. See which URI Protocol option shows your GET variables and use it.
In my case, I can see what I need in
REQUEST_URI
// defeat stupid CI GET squashing!
parse_str($_SERVER['REQUEST_URI'], $_GET);
This places your query string back into the $_GET super global for that page instance (You don't have to use $_GET, it can be any variable.)
EDIT
Since posting this I found that when using REQUEST_URI, you will lose your first query string array key unless you remove everything before the ?. For example, a URL like /controller/method?one=1&two=2 will populate the $_GET array in this example with array('method?one'=>1,'two'=>2). To get around this, I used the following code:
parse_str(substr(strrchr($_SERVER['REQUEST_URI'], "?"), 1), $_GET);
I suppose I should have provided an example, so here goes:
class Pgate extends Controller {
function postback() {
parse_str(substr(strrchr($_SERVER['REQUEST_URI'], "?"), 1), $_GET);
$receipt = $this->input->xss_clean($_GET['receipt']);
}
}
If you want the unparsed query string:
$this->input->server('QUERY_STRING');
// 98% functional
parse_str($_SERVER['REQUEST_URI'], $_GET);
This in fact is the best way to handle the lack of support for $_GET query strings in CodeIgniter. I actually came up with this one on my own myself, but soon realized the same thing Bretticus did in that you had to slightly modify the way you treated the first variable:
// 100% functional
parse_str(substr(strrchr($_SERVER['REQUEST_URI'], "?"), 1), $_GET);
It was only going to be a matter of time before I got to it myself, but using this method is a better one-line solution to everything else out there, including modifying the existing URI library, is isolated to only the controller where it is applicable, and eliminates having to make any changes to the default configuration (config.php)
$config['uri_protocol'] = "AUTO";
$config['enable_query_strings'] = FALSE;
With this, you now have the following at your disposal:
/controller/method?field=value
/controller/method/?field=value
Verify the results:
print_r($_GET); // Array ( [field] => value )
Open up application/config/config.php and set the following values:
$config['uri_protocol'] = "PATH_INFO";
$config['enable_query_strings'] = TRUE;
Now query strings should work fine.
If you're using mod_rewrite to remove the index.php file, you can use the following code to obtain the GET variables (via $this->input->get()). Assuming the default configuration, name the file MY_Input.php and place it in your application/libraries directory.
Usage: $this->input->get()
class MY_Input extends CI_Input {
function My_Input()
{
parent::CI_Input();
// allow GET variables if using mod_rewrite to remove index.php
$CFG =& load_class('Config');
if ($CFG->item('index_page') === "" && $this->allow_get_array === FALSE)
{
$_GET = $this->_get_array();
}
}
/**
* Fetch an item from the GET array
*
* #param string $index
* #param bool $xss_clean
*/
function get($index = FALSE, $xss_clean = FALSE)
{
// get value for supplied key
if ($index != FALSE)
{
if (array_key_exists(strval($index), $_GET))
{
// apply xss filtering to value
return ($xss_clean == TRUE) ? $this->xss_clean($_GET[$index]) : $_GET[$index];
}
}
return FALSE;
}
/**
* Helper function
* Returns GET array by parsing REQUEST_URI
*
* #return array
*/
function _get_array()
{
// retrieve request uri
$request_uri = $this->server('REQUEST_URI');
// find query string separator (?)
$separator = strpos($request_uri, '?');
if ($separator === FALSE)
{
return FALSE;
}
// extract query string from request uri
$query_string = substr($request_uri, $separator + 1);
// parse query string and store variables in array
$get = array();
parse_str($query_string, $get);
// apply xss filtering according to config setting
if ($this->use_xss_clean === TRUE)
{
$get = $this->xss_clean($get);
}
// return GET array, FALSE if empty
return (!empty($get)) ? $get : FALSE;
}
}
Thanks to all other posters. This is what hit the spot for me:
$qs = $_SERVER['QUERY_STRING'];
$ru = $_SERVER['REQUEST_URI'];
$pp = substr($ru, strlen($qs)+1);
parse_str($pp, $_GET);
echo "<pre>";
print_r($_GET);
echo "</pre>";
Meaning, I could now do:
$token = $_GET['token'];
In the .htaccess i had to change:
RewriteRule ^(.*)$ /index.php/$1 [L]
to:
RewriteRule ^(.*)$ /index.php?/$1 [L]
Here's a full working example of how to allow querystrings in Codeignitor, like on JROX platform. Simply add this to your config.php file located at:
/system/application/config/config.php
And then you can simply get the querystrings like normal using $_GET or the class below
$yo = $this->input->get('some_querystring', TRUE);
$yo = $_GET['some_querystring'];
Here's the code to make it all work:
/*
|--------------------------------------------------------------------------
| Enable Full Query Strings (allow querstrings) USE ALL CODES BELOW
|--------------------------------------------------------------------------*/
/*
|----------------------------------------------------------------------
| URI PROTOCOL
|----------------------------------------------------------------------
|
| This item determines which server global should
| be used to retrieve the URI string. The default
| setting of 'AUTO' works for most servers.
| If your links do not seem to work, try one of
| the other delicious flavors:
|
| 'AUTO' Default - auto detects
| 'PATH_INFO' Uses the PATH_INFO
| 'QUERY_STRING' Uses the QUERY_STRING
| 'REQUEST_URI' Uses the REQUEST_URI
| 'ORIG_PATH_INFO' Uses the ORIG_PATH_INFO
|
*/
if (empty($_SERVER['PATH_INFO'])) {
$pathInfo = $_SERVER['REQUEST_URI'];
$index = strpos($pathInfo, '?');
if ($index !== false) {
$pathInfo = substr($pathInfo, 0, $index);
}
$_SERVER['PATH_INFO'] = $pathInfo;
}
$config['uri_protocol'] = 'PATH_INFO'; // allow all characters
$config['permitted_uri_chars'] = ''; // allow all characters
$config['enable_query_strings'] = TRUE; // allow all characters
parse_str(substr(strrchr($_SERVER['REQUEST_URI'], "?"), 1), $_GET);
Enjoy :-)
Set your config file
$config['index_page'] = '';
$config['uri_protocol'] = 'AUTO';
$config['allow_get_array'] = TRUE;
$config['enable_query_strings'] = FALSE;
and .htaccess file (root folder)
<IfModule mod_rewrite.c>
Options +FollowSymLinks
Options -Indexes
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond $1 !^(index\.php)
RewriteRule ^(.*)$ index.php [L]
</IfModule>
Now you can use
http://example.com/controller/method/param1/param2/?par1=1&par2=2&par3=x
http://example.com/controller/test/hi/demo/?par1=1&par2=2&par3=X
server side:
public function test($param1,$param2)
{
var_dump($param1); // hi
var_dump($param2); // demo
var_dump($this->input->get('par1')); // 1
var_dump($this->input->get('par2')); // 2
var_dump($this->input->get('par3')); // X
}
You could make a rule in your .htaccess to prevent your MOD_REWRITE from firing on that specific page. That should allow you to use the _GET.
Here is how i did it recently. Hope it helps
<?php
//adapt this code for your own use
//added example.com to satisfy parse_url
$url="http://www.example.com".$_SERVER["REQUEST_URI"];
$url=parse_url($url);
//I'm expecting variables so if they aren't there send them to the homepage
if (!array_key_exists('query',$url))
{
redirect('/'); exit;
}
$query=$url['query'];
parse_str($query,$_GET); //add to $_GET global array
var_dump($_GET);
?>
to call : http://www.mydomain.com/mycontroller/myfunction/?somestuff=x&morestuff=y
You can create a pre_system hook. In the hook class you create, you can grab the desired query params and add them to the $_POST for normal CI processing. I did this for a jQuery Ajax helper.
For instance:
(Name this file autocomplete.php or whatever you put as the file name in the hook)
<?php
/*
By Brodie Hodges, Oct. 22, 2009.
*/
if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
* Make sure this file is placed in your application/hooks/ folder.
*
* jQuery autocomplete plugin uses query string. Autocomplete class slightly modified from excellent blog post here:
* http://czetsuya-tech.blogspot.com/2009/08/allowing-url-query-string-in.html
* Ajax autocomplete requires a pre_system hook to function correctly. Add to your
* application/config/hooks.php if not already there:
$hook['pre_system'][] = array(
'class' => 'Autocomplete',
'function' => 'override_get',
'filename' => 'autocomplete.php',
'filepath' => 'hooks',
'params' => array()
);
*
*
*/
class Autocomplete {
function override_get() {
if (strlen($_SERVER['QUERY_STRING']) > 0) {
$temp = #array();
parse_str($_SERVER['QUERY_STRING'], $temp);
if (array_key_exists('q', $temp) && array_key_exists('limit', $temp) && array_key_exists('timestamp', $temp)) {
$_POST['q'] = $temp['q'];
$_POST['limit'] = $temp['limit'];
$_POST['timestamp'] = $temp['timestamp'];
$_SERVER['QUERY_STRING'] = "";
$_SERVER['REDIRECT_QUERY_STRING'] = "";
$_GET = #array();
$url = strpos($_SERVER['REQUEST_URI'], '?');
if ($url > -1) {
$_SERVER['REQUEST_URI'] = substr($_SERVER['REQUEST_URI'], 0, $url);
}
}
}
}
}
?>

Categories