Background
I have been working through various tutorials over the past couple of months and am currently trying understand PHP frameworks.
One of the ways I am doing this is by trying to design my own very simple MVC framework from scratch.
I am trying to re-factor an application (which I have already built using spaghetti procedural PHP). This application has a front end for teachers and a back-end for the administrators.
I would like to separate concerns and have URL's like this
http://example.com/{module}/{controller}/{method}/{param-1}/{param-2}
Now the MVC framework I have cobbled together up to this point does not handle routing for 'modules' (I apologise if this is not the correct terminology), only the controller/method/params.
So I have separated the public_html from the app logic and inside of the /app/ folder I have specified two folders, my default "learn module" and the "admin module" so that the directory tree looks like this:
Apparently this design pattern is a "H"MVC?
My Solution
I am basically making use if the is_dir(); function to check if there is a "module" directory (such as "admin") and then unsetting the first URL array element $url[0] and reindexing the array to 0... then I am changing the controller path according to the URL... the code should be clearer...
<?php
class App
{
protected $_module = 'learn'; // default module --> learn
protected $_controller = 'home'; // default controller --> home
protected $_method = 'index'; // default method --> index
protected $_params = []; // default parameters --> empty array
public function __construct() {
$url = $this->parseUrl(); // returns the url array
// Checks if $url[0] is a module else it is a controller
if (!empty($url) && is_dir('../app/' . $url[0])) {
$this->_module = $url[0]; // if it is a model then assign it
unset($url[0]);
if (!empty($url[1]) && file_exists('../app/' . $this->_module . '/controllers/' . $url[1] . '.php')) {
$this->_controller = $url[1]; // if $url[1] is also set, it must be a controller
unset($url[1]);
$url = array_values($url); // reset the array to zero, we are left with {method}{param}{etc..}
}
// if $url[0] is not a module then it might be a controller...
} else if (!empty($url[0]) && file_exists('../app/' . $this->_module . '/controllers/' . $url[0] . '.php')) {
$this->controller = $url[0]; // if it is a controller then assign it
unset($url[0]);
$url = array_values($url); // reset the array to zero
} // else if url is empty default {module}{controller}{method} is loaded
// default is ../app/learn/home/index.php
require_once '../app/' . $this->_module . '/controllers/' . $this->_controller . '.php';
$this->_controller = new $this->_controller;
// if there are methods left in the array
if (isset($url[0])) {
// and the methods are legit
if (method_exists($this->_controller, $url[0])) {
// sets the method that we will be using
$this->_method = $url[0];
unset($url[0]);
} // else nothing is set
}
// if there is anything else left in $url then it is a parameter
$this->_params = $url ? array_values($url) : [];
// calling everything
call_user_func_array([$this->_controller, $this->_method], $this->_params);
}
public function parseUrl() {
// checks if there is a url to work with
if (isset($_GET['url'])) {
// explodes the url by the '/' and returns an array of url 'elements'
return $url = EXPLODE('/', filter_var(rtrim($_GET['url'], '/'), FILTER_SANITIZE_URL));
}
}
}
this so far appears to be working for me, but.....
Question
I am not sure if this is the preferred solution to this issue. Is calling the is_dir() check for every page request going slow down my app?
How would you engineer a solution or have I completely misunderstood the issue?
Many thanks in advance for your time and consideration!!
In my experience, I often use .htaccess file to redirect any request to an only index.php file, both these files place in the public_html folder.
The content of .htaccess file as following (your apache server should enabled mod_rewrite):
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
Then, in the index.php file you can parse request url, define configs, common variables, paths, etc... and include neccessary others php files.
An example:
require( dirname( __FILE__ ) . '/your_routing.php' );
require( dirname( __FILE__ ) . '/your_configs.php' );
require( dirname( __FILE__ ) . '/admin/yourfile.php' );
...
I'm trying to create a hyperlink structure that can access multi-level sub folders. For now, I can access my hyperlinks with only one-level (php) directory, such like index.php?content=about (whereas 'about' is about.php).
What I want to do is to create a filing system for a larger website with multi-level sub-directories like the following example,
index.php?blog/category/process/. I don't know if there is a symbol that replaces html symbol / (slash for directory) but for PHP directories. I tried different ways of accessing files inside multi-level sub-directories, such as putting the ? (question mark). It was all hypothetically experimental. If I use the slash, such in the form 'blog/category/process/filename.php', I get a 404 Not Found error.
There is the following PHP script inside function.php, that gets the content from the URL, if there is no content, it sets a default and if there is content, it sanitizes data against hacking:
function loadContent($where, $default='') {
$content = filter_input(INPUT_GET, $where, FILTER_SANITIZE_STRING);
$default = filter_var($default, FILTER_SANITIZE_STRING);
$content = (empty($content)) ? $default : $content;
if ($content) {
$html = include 'content/'.$content.'.php';
return $html;
}
}
function loadIncludes($where, $default='includes/') {
$includes = filter_input(INPUT_GET, $where, FILTER_SANITIZE_STRING);
$default = filter_var($default, FILTER_SANITIZE_STRING);
if($includes) {
$html = include 'includes/'.$includes.'.php';
return $html;
}
}
The link to the document inside 'blog/category/process/filename.php' shows up (where it was '404 Error' before, but the header and the css files do not work. They are not been picked up.
Please note:
My website structure is
Header (one header picked up by index.php?)
Content (multiple content=filename.php)
Footer
index.php looks like this:
<?php
require ('includes/function.php');
require ('includes/init.php'); /* init.php picks up the header */
?>
<div class="clearboth"></div>
<!-- ******** HOMEPAGE ********** -->
<?php loadContent('content', 'home'); ?>
<div class="clearboth"></div>
<!-- ******** FOOTER ********** -->
<?php include ('content/footer.php'); ?>
I found the solution on the structure of the multi-level sub-directories through index.php? that also picks up the CSS, which is as follows:
<a href="index.php?content=blog/category/process/filename">
(leave out the extension .php followed after 'filename')
Thank you for all your help!
<?php
/* FOLDERS STRUCTURE
$_SERVER['DOCUMENT_ROOT'] = C:/Server/www/music; // YES Windows :)
THIS FILE = folders.php
C:/Server/www/music
C:/Server/www/music/files/
C:/Server/www/music/files/folder1/
C:/Server/www/music/files/folder1/folder2/myfile.jpeg
*/
$root = $_SERVER['DOCUMENT_ROOT'];
$requestURI = $_SERVER['REQUEST_URI'];
$startFolder = '/files/';
preg_match('/\?/', $requestURI) ? list($requestURI, $fileToFind) = explode('?', $_SERVER['REQUEST_URI']) : exit('Bad request');
$file = $root.$startFolder.$fileToFind;
if(file_exists($file)){
//YOUR CODE
}
else {
header("HTTP/1.0 404 Not Found");
}
// Usage = http://music.com/folders.php?/folder1/folder2/myfile.jpeg
?>
Read those questions I had asked before. I hope it will help you
.htaccess : Redirect all http requests to one file whitout 404 resp. code
Use PHP to rewrite URLS?
Note that you may play with http headers header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found"); or header($_SERVER["SERVER_PROTOCOL"]." 200 Ok"); In case the file exists or not
Active your imagination...
I found the solution on the hyperlink structure of a multi-level sub-directories through index.php? that also picks up the CSS, and does not issue a 404 Error, which is as follows:
<a href="index.php?content=blog/category/process/filename">
descriptive header
</a>
(note: remember to leave out the extension .php followed after 'filename')
Basically what the title says. I'm working on a project that does not involve classes and I would like to implement htaccess and mod rewrite to create pretty url's. Here are a few examples of what I want to be able to route. My file and directory structure is laid out below as well.
/root
- index.php (bootstrap)
/template
/sources
/forums
-threads.source.php
-discussions.source.php
-post.source.php
-index.source.php
//
- members.source.php (functions for register, login, etc)
- index.source.php
http://localhost/myapp/members/register -> sources/members.source.php register function
http://localhost/myapp/ -> sources/index.source.php
http://localhost/myapp/forums/threads/Some-Cool-Thread/0001 -> sources/forums/threads.source.php view function
http://localhost/myapp/forums/ ->sources/forums/index.source.php
I hope this makes sense to you. By the way: I am using .source.php as file names because I will also have template files that will use, for example, members.template.php
EDIT: I have already made my .htaccess file and have also gotten the URI string in my index file. I have a bit that validates the uri segment characters. Here's my .htaccess file:
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^(.*?) index.php
Here is my index file:
<?php
session_start();
require_once('config.php');
global $config;
date_default_timezone_set($config['server']['default_timezone']);
define('DOC_ROOT', dirname(__FILE__));
if(!mysql_connect($config['db']['server'], $config['db']['user'], $config['db']['pass'])){
die('System Failure: Could not connect to the requested database server. It may be down or it may be too busy! Please check back later.');
} else if(!mysql_select_db($config['db']['name'])){
die('System Failure: Could not connect to the requested database server. It may be down or it may be too busy! Please check back later.');
} else {
$uri_segments = $_SERVER['REQUEST_URI'];
$uri_segments = str_replace('/Base%20Command/', '', $uri_segments);
$uri_segments = explode('/', $uri_segments);
foreach($uri_segments as $segment){
if(!preg_match('^[a-z0-9_-]^', $segment)){
die('Could not accept provied URI string... the string contains invalid characters.');
}
}
if(file_exists(DOC_ROOT.'/sources/global.source.php')){
require_once(DOC_ROOT.'/sources/global.source.php');
}
if(file_exists(DOC_ROOT.'/template/global.template.php')){
require_once(DOC_ROOT.'/template/global.template.php');
if(function_exists('html_header')){
$output = html_header();
if(file_exists(DOC_ROOT.'/template/'.$source.'.template.php')){
require_once(DOC_ROOT.'/template/'.$source.'.template.php');
if(function_exists($action)){
$output .= $action();
} else {
$output .= Error404();
}
} else {
$output .= Error404();
}
if(function_exists('html_footer')){
$output .= html_footer();
}
} else {
$output = 'System Failure: Certain template files did not load correctly.';
}
echo $output;
}
}
?>
Basically what I need to do is to find a way to efficiently come up with values for the source and action variables.
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
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;
}
}
?>