Jump to content

[Tutorial] Clean PHP URL handler


boen_robot

Recommended Posts

PrefaceAfter seeing enough worthless "tutorials" posted in this forum, I decided to post this one of mine, hoping it would serve as an example of a better tutorial, or burn in flames like the rest. Of course, I'm also hoping it would prove useful to everyone that reads it. Since this is after all, a forum, feel free to leave any comments and suggestions, or whole tutorials on your own that offer a different approach to the problem at hand.This tutorial assumes basic understanding of Apache's configuration directives, regular expressions and assumes your documentRoot to be at "D:\htdocs".Abstract/Intro/Why would you want a PHP URL handler anyway?Everybody wants and eventually either uses or makes their own type of content management system. We always need to maintain content across all (or some) pages (menu being the most common), possibly offer multiple languages (while still maintaining the layout and all), and so on. One way of easily achieving that goal, which also offers the possibility of separaing the PHP from the HTML is to use a single file to do all processing. All URLs on the site refer to this single file with different query string variables to indicate various preferences, including the location of the page at hand. A typical URL on sites like that may look like

http://example.com/?catgetory=images&file=myPicture&action=comments&lang=en

Current solutionsThere are already a lot of tutorials that can show you how to use Apache's mod_rewrite to turn that URL into

http://example.com/en/comments/images/MyPicture/

or something similar. This is nice, but decreases your flexibility a lot. Whatever fine rule you create to map the first directory to the "lang" query string variable, you'll have to go again into the server configuration when you want to add an additional directory. Also, does anyone have an idea how exactly rewrites behave when you miss a certain "folder"? That is, if you have for example just

http://example.com/en/comments/images/

As far as I've tried, you need to have a second rewrite rule to deal with this. And that for each level up the chain. So, when you want to add a cetain folder down the chain, you need to write yet another rewrite rule. I'd say that's very uncomfortable. Besides, mod_rewrite is not available on all servers, so if you want to make your application portable (i.e. work with or without mod_rewrite), you need some way of detecting its availability.Another ways is to simply have

http://example.com/index.php?/en/comments/images/MyPicture/

and parse the query string manually. While portable, this approach makes it harder to add additional data you may feel more comfortable leaving in the query string. I for one believe personal preferences like language for example are better left in the query string (in the case of the language, a subdomain is also good if you can afford it).This tutorials will NOT show you how to do theese. Instead, it offers an alternative approach that attempts to adress some of the issues that arise by using the above ideas by combining them into something new, that has actually been available for a long time.The new simple solutionPerhaps "simple" is a little exaggerated, but it still combines the best of both worlds. Ready................................. AliasMatch. Didn't saw that one coming, did you? AliasMatch is probably the easiest way to send URLs matching a certain criteria to a single file. Unlike mod_rewrite, you don't have to deal with the query string, as its not part of the whole process. The simple rule you need to add to your httpd.conf is

AliasMatch ^/[^\_].*/ "D:/htdocs/index.php"

Replacing "\_" with any character or character range you'd like. That is a prefix that will be used to "disable" the PHP file aliasing. With the rule above, "_" is that character. So, all URLs, except the ones starting with "/_" will be aliased to the PHP file. So

http://example.com/en/comments/images/MyPicture/

and

http://example.com/en/comments/images/

and

http://example.com/index.php?/en/comments/images/MyPicture/

are all aliased to

D:/htdocs/index.php

but

http://example.com/_myStaticFile.php

and

http://example.com/_static/style.css

are not.How exactly does all that help? Well, PHP's $_SERVER variable looks a little different because of the aliasing. In particular, $_SERVER['SCRIPT_NAME'] holds the path before the aliasing without including the query string. If you do add a query string variable, it will be available to PHP as it would be without the aliasing. So, if we have index.php containing

<?phpecho $_SERVER['SCRIPT_NAME'] . "\n\n", print_r($_GET, true);?>

And we access

http://example.com/comments/images/MyPicture/?lang=en

the output will be

/comments/images/MyPicture/Array(	[lang] => en)

We can then only parse the path manually to lead us to the file we want to reach, and read the query string variables as usual. This gives a clean separation of "where" (path) our content is and "how" (query string) do we give it. It's also far easier to do than any mod_rewrite approach.The pitfall of this approachLike all solutions before, this one too has its pitfall. In this case, this is the support you'll find for this with hosts, or the lack of. The reason is quite simple - aliases are only allowed in configuration files (like httpd.conf) possibly under a <VirtualHost> section. They are not allowed in .htaccess files. Not all hosts allow you to use .htaccess files, and even the ones that are generous enough to allow you to use .htaccess files will never allow you to use your own configuration files. In order to apply settings from such files, the server must be restarted, and no host would want to restart their web server every minute, just because one of their members changed one of its configuration files.The only places where this technique may come in handy are dedicated hosting services, which allow you to do anything to the server you bought from them, or if you're hosting the site on your own server. On shared hosts, mod_rewrite is still the only solution.Getting the best of all worldsIf you apply an alias at the main configuration file, and apply mod_rewrite settings in .htaccess file, you don't have to worry for the scenario about both of them running. The alias will take precendance. However, in the case when URL rewriting occurs, the $_SERVER['SCRIPT_NAME'] won't be adjusted to the path before the rewriting. Fortunatly, you could adjust that value from within your PHP, depending on another condition, such as the existense of a special query string variable you'll include during the URL rewriting, or an environemental variable that you'll set with the E flag on the RewriteRule directive (a tactic, which btw, I failed to implement so far - it always appears as if I've never set that variable).If you must use mod_rewrite, your best bet is an .htaccess file like:

RewriteEngine OnRewriteRule ^([^\_].*)/ index.php?uri=%{REQUEST_URI}&%{QUERY_STRING}

This time, we include the requested URI in a query string variable (called "uri"). Now, our PHP file can react accodingly. As our very first line (or anywhere at the start while we don't do any real processing), we could add:

$_SERVER['SCRIPT_NAME'] = (isset($_GET['uri']) ? ($_GET['uri'] === '/' ? '/' . basename(__FILE__) : $_GET['uri']) : $_SERVER['SCRIPT_NAME']);

and keep coding on, assuming $_SERVER['SCRIPT_NAME'] will always hold the right value. The part

($_GET['uri'] === '/' ? '/' . basename(__FILE__) : $_GET['uri'])

is a fix for a special case in $_SERVER['SCRIPT_NAME']. Regardless of whether you have "/" or "/index.php" as the URL, $_SERVER['SCRIPT_NAME'] gets "/index.php". However, when you rewrite the URL "/", it will be rewritten to the "uri" query string variable, having "/" as a value. The code portion above will make that URL appear as if you've accessed "/index.php" instead.If you want to somehow have nicer URLs even when mod_rewrite is disabled/not available and there's no option for aliases either, you could check if the query string is parsable as an array, and assume mod_rewrite is turned on if it is.Using environmental variables instead of query string variables would provide you with yet another separation of concerns, as the "where", "how", and "in what conditions" concerns are now separated. Unfortunatly though, as said already, I failed to do this on my own. The rule that should've done the trick is:

RewriteEngine OnRewriteRule ^([^\_].*)/ index.php?uri=%{REQUEST_URI}&%{QUERY_STRING} [E=rw:on]

and within the PHP file, you'd use

getenv('rw') == 'on'

to see if the rewriting rule was applied (and assume either alias or no alias if it's off, depending on possibly a different value of this variable) and act accordingly.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...