Manage URL routes in own php framework

2019-03-12 22:52发布

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.

12条回答
孤傲高冷的网名
2楼-- · 2019-03-12 23:27

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.

查看更多
萌系小妹纸
3楼-- · 2019-03-12 23:27

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 :(

查看更多
姐就是有狂的资本
4楼-- · 2019-03-12 23:27

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

查看更多
你好瞎i
5楼-- · 2019-03-12 23:28

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 '<a href="'.fixpath('/admin/logout').'">link</a>';

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.

查看更多
疯言疯语
6楼-- · 2019-03-12 23:35

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.

查看更多
Luminary・发光体
7楼-- · 2019-03-12 23:37

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 ...

查看更多
登录 后发表回答