I have built my own PHP MVC Framework after following some tutorials online. I have this all working using an entry script in .htaccess as follows:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?rt=$1 [L,QSA]
I have a Router class which basically translates the URL and divides it in to action / controller segments:
$route = (empty($_GET['rt'])) ? '' : $_GET['rt'];
if (empty($route))
{
$route = 'index';
}
else
{
/*** get the parts of the route ***/
$parts = explode('/', $route);
$this->controller = $parts[0];
if(isset( $parts[1]))
{
$this->action = $parts[1];
}
}
What I want to do is take this one step further and actually define URL rewriting rules that work in addition to the automatic routing. So basically I want to be able to do something like this:
$rules = array(
'directory/listing/<id>' => 'listing/index',
'directory/<category>/<location>' => 'directory/view',
);
In the above, the array key is the entered URL - the parts in the angle brackets are dynamic variables (like GET variables). The array value is where the request needs to be routed to (controller/action). So for the above two rules we have the following two actions:
public function actionIndex($id) {
}
public function actionView($category, $location) {
}
So essentially the URL needs to checked against the array keys first, if it matches one of the keys then it needs to use the array value as the controller/action pair.
Anybody have any ideas how to go about this?
I'm not sure of the exact code you would need to implement this in your environment, but I can mention that this is exactly how Magento's URL rewrites function. Their URLs are structured as <controller>/<action>/<arguments>. They even take it one step further and define key/value pairs as URL parameters so ../id/20/meta/25 turns into an array like so:
array(
'id' => 20,
'meta' => 25
)
You could download their source and check it out. It may help you head in the right direction:
Related
I am trying to figure out the best approach when linking to static pages using a loosely followed MVC design pattern.
I begin by rewriting all requests to the index.php which handles all request and break them down the url into the controller, action and parameters. However if i don't want to follow this url structure and just want to visit a static page such as 'http://example.com/home/' without having to call some action how would i achieve this without getting a php error caused by my router/dispatcher trying to request a file that does not exist?
I thought about setting up some switch statement or a if statement as shown below that checks if the url is set to something then uses a custom defined controller and action, or i wasn't sure whether to take the static resources out of the MVC directory and have it seperate and link to it that way?
<?php
class Router
{
static public function parse($url, $request)
{
$url = trim($url);
if ($url == "/")
{
$request->controller = "tasks";
$request->action = "index";
$request->params = [];
}
else
{
$explode_url = explode('/', $url);
$explode_url = array_slice($explode_url, 2);
$request->controller = $explode_url[0];
$request->action = $explode_url[1];
$request->params = array_slice($explode_url, 2);
}
}
}
?>
This works, but i'd rather not have a huge router setup for many different static resources as it feels tacky and that i am just patching together code. Would putting static pages in its own directory outside of MVC and linking to them in the views be a valid option? i'm relatively new to MVC so any guidance would be great.
Your application shouldn't receive request it is not supposed to handle, you can solve this on a webserver level:
if you are using apache for example, you can setup in the .htaccess file that the request should be directed to your front controller (ex: index.php) only if the requested resource does not exist
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.*$ /index.php [L]
I have a problem building a front controller for a project I have at school.
Here is the thing, i created several controllers per entities i.e. One to select, another one to delete etc... and this for posts and comments. So i have 6 controllers and then I'm being asked to build front controller above these controllers but i keep on adding layers (route all to index.php then from there I instantiate the front controller which then will send the request to the related controller with header(location:...) and it gets messy and very complicated because for some of the request i need to pass some ids in the url for the db request...
I can't use any framework so is there a cleaner way to build a front controller that can handle request with parameters in the url and some without?
Do i have to route everything to a single page (with htaccess i created a simple rule to send everything to index.php)?
And then from there again instantiate the front controller?
If you need some code I'll share this with you but it's a mess :)
Thanks guys, i need advise I'm kind of lost at this point
You will want to build a basic router, what it does.
A router will parse the path and take parts of it and locate a controller file. And then run a method in that file with arguments. We will use this format for urls
www.yoursite.com/index.php/{controller}/{method}/{args}
So for this example our url will be
www.yoursite.com/index.php/home/index/hello/world
the index.php can be hidden using some basic .htaccess ( Mod Rewrite )
So lets define some things, first we will have folders
-public_html
index.php
--app
---controller
home.php
So the main folder is public_html with the index.php and a folder named app. Inside app is a folder named controller and inside that is our home.php contoller
Now for the Code ( yea )
index.php (basic router )
<?php
//GET URI segment from server, everything after index.php ( defaults to home )
$path_info = isset( $_SERVER['PATH_INFO'] ) ? $_SERVER['PATH_INFO'] : '/home';
//explode into an array - array_filter removes empty items such as this would cause '/home//index/' leading /, trailing / and // double slashes.
$args = array_filter( explode('/', $path_info) );
$controller_class = array_shift($args); //remove first item ( contoller )
$method = count( $args ) > 0 ? array_shift($args) : 'index'; //remove second item or default to index ( method )
$basepath = __DIR__.'/app/controller/'; //base path to controllers
if(!file_exists($basepath.$controller_class.".php") ){
echo "SHOW 404";
exit();
}
//instantiate controller class
require_once $basepath.$controller_class.".php";
$Controller = new $controller_class;
//check if method exists in controller
if(!method_exists( $Controller, $method ) ){
echo "Method not found in controller / or 404";
exit();
}
//call methods with any remaining args
call_user_func_array( [$Controller, $method], $args);
home.php ( controller )
<?php
class home{
public function index( $arg1="", $arg2=""){
echo "Arg1: ".$arg1 . "\n";
echo "Arg2: ".$arg2 . "\n";
}
public function test( $arg1 = "" ){
echo "Arg1: ".$arg1 . "\n";
}
}
Now if you put in any of these urls
www.yoursite.com/index.php
www.yoursite.com/index.php/home
www.yoursite.com/index.php/home/index
It should print ( defaults )
Arg1:
Arg2:
If you do this url
www.yoursite.com/index.php/home/index/hello/world
It should print
Arg1: hello
Arg2: world
And if you do this one
www.yoursite.com/index.php/home/test/hello_world
it would print
Arg1: hello_world
The last one, running in the second method test ( no echo arg2 ), this way you can see how we can add more controllers and methods with only having to code them into a controller.
This method still allows you to use the $_GET part of the url, as well as the URI part to pass info into the controller. So this is still valid
www.yoursite.com/index.php/home/test/hello_world?a=1
and you could ( in home::test() ) output the contents of $_GET with no issues, this is useful for search forms etc. Some pretty url methods prevent this which is just ... well ... crap.
In .htaccess with mod rewrite you would do this to remove index.php from the urls
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]
</IfModule>
Then you can use urls without the index.php such as this
www.yoursite.com/home/index/hello/world
This is the simplest Router I could come up with on such short notice, yes I just created it. But this is a very similar ( although simplified ) implementation used in many MVC frameworks
PS. please understand how this is all done, so you actually learn something...
Many improvements could be made, such as allowing these urls
www.yoursite.com/hello/world
www.yoursite.com/home/hello/world
www.yoursite.com/index/hello/world
Which would all fall back to the a default of home controller and index method, but that would take some additional checks (for not found files and methods ) I cant be bothered with right now...
Here's a simple demo process (won't work, just an example).
index.php
//Say you called /index.php/do/some/stuff
$route = $_SERVER["PATH_INFO"]; //This will contain /do/some/stuff
$router = new Router();
$router->handle($route);
router.php
class Router {
private static $routeTable = [
"do/some/stuff" => [ "DoingSomeController" => "stuff" ]
];
public function handle($route) {
if (isset(self::$routeTable[trim($route,"/"])) {
$controller = new self::$routeTable[trim($route,"/"][0];
call_user_func([$controller,self::$routeTable[trim($route,"/"][1]]);
}
}
}
Basic demo, what some frameworks do.
I am trying to create custom routes for my CodeIgniter site. Even the most basic routes do not work. For example I have the welcome controller mapped to "test" and it just 404's on me. I am running on MAMP with mod_rewrite enabled.
I have the index.php line in config.php empty..
$config['index_page'] = '';
Here is my .htacess file..
DirectoryIndex index.php
RewriteEngine on
RewriteCond $1 !^(index\.php|images|css|js|robots\.txt|favicon\.ico)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ ./index.php/$1 [L,QSA]
And here is my route..
$route['welcome'] = 'test';
In a route, the array key contains the URI to be matched, while the
array value contains the destination it should be re-routed to.
-- CI Documentation
Your physical controller name is welcome. So if you want a URL containing the word test in the first segment be remapped to the welcome class, you should do this:
$route['test'] = "welcome/METHOD";
Where METHOD is method of welcome class.
Note: If class/method was welcome/index, you do NOT need to append /index.
If I read this correctly, that route will try to redirect the controller 'welcome' to the controller 'test'. If you have a controller named 'test' and a function named 'index', you can route the following:
route['welcome/index'] = 'test/index';
Is that what you are trying to do?
A couple things:
Routes in CI are cascaded, they're evaluated top to bottom, and the router stops at the first match. Make sure, then, to have any custom route placed below the 2 default routes in a vanilla distribution:
$route['default_controller'] = "welcome";
$route['404_override'] = '';
// custom routes here
$route['welcome'] = "welcome/test";
If you have a controller named "welcome" (the default one), and you want to call a method named "test", you need a route like
$route['welcome'] = "welcome/test"
which will be accessible at the url http://wwww.yourdomain.com/welcome
(if no route were specified, you would have accessed it like http://www.yourdomain.com/welcome/test)
Usually, controller have an index method which is called automatically when no other method is provided. The route you've created so far isn't working because it's calling the index() method of a "test" controller, which is likely not present.
A suggestion: if you mainatain the "welcome" controller as the default one, and you want to call an url like http://www.yourdomain.com/test
You need a test() method and your route must be
$route['test'] = "welcome/test";
I was wondering how one could make codeigniter style url segments on a project.
Aside from an htaccess rule to capture the segments, how can this be done in PHP?
After snooping around the codeigniter source code, i could not find any reference to an htaccess file that captures the usage.
Any idea on how this can be done?
Assuming you are passing ALL requests to index.php via Apache mod_rewrite or Nginx:
// Get the current URL path (only) and convert encoded characters back to unicode
$path = rawurldecode(trim(parse_url(getenv('REQUEST_URI'), PHP_URL_PATH), '/')));
// array merge with defaults
$segments = explode('/', $path) + array('home', 'index');
//First two segments are controller/method
$controller = array_shift($segments);
$method = array_shift($segments);
// #todo serious security checking here!
// Finally, load and call
$controller = new $controller;
$controller->$method($segments);
Your controller would look like this
class Home
{
public function index($params)
{
print_r($params);
}
}
What you do is set up one single url param in htaccess, and then use a string splitting method to retrieve a model, controller, and view from that string which will then call a model class, a controller class, and then render the data into a view. the transformation would be something like the following:
mysite.com/index.php?url=/tasks/all => mysite.com/tasks/all
which calls the task model, which then calls the function called "all()" inside of the tasks controller.
As soon as this site goes back online, do look at the htaccess portion of the tutorial -- he did a good job of showing how its done http://www.henriquebarroso.com/how-to-create-a-simple-mvc-framework-in-php/
You still need something to "forward" all virtual requests to a physical file.
The idea is that any URI that doesn't match a physical file or folder on disk is rewritten (usually through mod_rewrite) to your index.php file (it's usually your index file so a direct call to the index works too), and it appends the URI to the path or as a query string parameter:
RewriteCond %{REQUEST_FILENAME} !-f # Not a real file
RewriteCond %{REQUEST_FILENAME} !-d # Not a real folder
RewriteRule ^(.*)$ index.php/$1 [L] # Rewrite to index.php, with a leading slash, followed by the URI
Alternatively, you can use a standard error document handler (still in .htaccess or apache config, but no need for mod_rewrite!):
<IfModule !mod_rewrite.c>
ErrorDocument 404 /index.php
</IfModule>
Now that control is passed to uniformly to an index.php file, you need a router mechanism to match the route to the correct controller. Ideally, you'd have a list of static and dynamic routes. Static routes are direct matches to the URI, and dynamic would be regular expression matches. Check your static routes first, as it's as simple as a single hash lookup, while you'll have to loop through all the dynamic routes.
For performance, it's nice to put your more common dynamic routes at the beginning of the list, and the obscure ones at the end.
Using the .htaccess file to employ mod_rewrite to rewrite the base of the url is essential to this. Otherwise, the browser will treat each segment as a supposed folder or file (depending on whether or not you have a trailing /)
once you have that, however, you can simply use the method described in this post:
how do i parse url php
I am setting up a simple routing system for my new custom MVC framework that I am making.
Currently my router class views the URL as such:
www.example.com/controller/controller_action/some/other/params
So, essentially...I've been reserving the first two segments of the URI for controller routing. However, what if I just want to run the following?
www.example.com/controller/some/other/params
...which would attempt to just run the default controller action and send the extra parameters to it?
Here is the simple router I'm using:
\* --- htaccess --- *\
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?rt=$1 [L,QSA]
\* --- index.php --- *\
if (array_key_exists('rt',$_GET)) {
$path = $_GET['rt'];
$uri = explode('/',$this->path);
if(empty($uri[0])) {
$load->ctrl('home');
}
elseif(empty($uri[1])) {
$load->ctrl($uri[0]);
}
else {
$load->ctrl($uri[0],$uri[1]);
}
}
else {
$load->ctrl('index');
}
\* --- loader class --- *\
public function ctrl($ctrl,$action=null) {
$ctrl_name = 'Ctrl_'.ucfirst(strtolower($ctrl));
$ctrl_path = ABS_PATH . 'ctrl/' . strtolower($ctrl) . '.php';
if(file_exists($ctrl_path)) { require_once $ctrl_path;}
$ctrl = new $ctrl_name();
is_null($action) ? $action = "__default" : $action = strtolower($action);
$ctrl->$action();
}
How can I do this?
You could handle this within your controller. Typically, MVC frameworks will call a default method when the requested method isn't available. Simply overwrite this fallback-method to call your desired method and pass the parameter list in as parameters.
For instance, KohanaPHP has the __call($method, $params) method that is called when the requested method doesn't exist. You could handle the logic within this, or its functional equivalent in your MVC framework..
This would let you keep the logic internal to the controller itself rather than having it blasted out between various files.