URL rewriting with PHP

286,316

Solution 1

You can essentially do this 2 ways:

The .htaccess route with mod_rewrite

Add a file called .htaccess in your root folder, and add something like this:

RewriteEngine on
RewriteRule ^/?Some-text-goes-here/([0-9]+)$ /picture.php?id=$1

This will tell Apache to enable mod_rewrite for this folder, and if it gets asked a URL matching the regular expression it rewrites it internally to what you want, without the end user seeing it. Easy, but inflexible, so if you need more power:

The PHP route

Put the following in your .htaccess instead: (note the leading slash)

FallbackResource /index.php

This will tell it to run your index.php for all files it cannot normally find in your site. In there you can then for example:

$path = ltrim($_SERVER['REQUEST_URI'], '/');    // Trim leading slash(es)
$elements = explode('/', $path);                // Split path on slashes
if(empty($elements[0])) {                       // No path elements means home
    ShowHomepage();
} else switch(array_shift($elements))             // Pop off first item and switch
{
    case 'Some-text-goes-here':
        ShowPicture($elements); // passes rest of parameters to internal function
        break;
    case 'more':
        ...
    default:
        header('HTTP/1.1 404 Not Found');
        Show404Error();
}

This is how big sites and CMS-systems do it, because it allows far more flexibility in parsing URLs, config and database dependent URLs etc. For sporadic usage the hardcoded rewrite rules in .htaccess will do fine though.

Solution 2

If you only want to change the route for picture.php then adding rewrite rule in .htaccess will serve your needs, but, if you want the URL rewriting as in Wordpress then PHP is the way. Here is simple example to begin with.

Folder structure

There are two files that are needed in the root folder, .htaccess and index.php, and it would be good to place the rest of the .php files in separate folder, like inc/.

root/
  inc/
  .htaccess
  index.php

.htaccess

RewriteEngine On
RewriteRule ^inc/.*$ index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]

This file has four directives:

  1. RewriteEngine - enable the rewriting engine
  2. RewriteRule - deny access to all files in inc/ folder, redirect any call to that folder to index.php
  3. RewriteCond - allow direct access to all other files ( like images, css or scripts )
  4. RewriteRule - redirect anything else to index.php

index.php

Because everything is now redirected to index.php, there will be determined if the url is correct, all parameters are present, and if the type of parameters are correct.

To test the url we need to have a set of rules, and the best tool for that is a regular expression. By using regular expressions we will kill two flies with one blow. Url, to pass this test must have all the required parameters that are tested on allowed characters. Here are some examples of rules.

$rules = array( 
    'picture'   => "/picture/(?'text'[^/]+)/(?'id'\d+)",    // '/picture/some-text/51'
    'album'     => "/album/(?'album'[\w\-]+)",              // '/album/album-slug'
    'category'  => "/category/(?'category'[\w\-]+)",        // '/category/category-slug'
    'page'      => "/page/(?'page'about|contact)",          // '/page/about', '/page/contact'
    'post'      => "/(?'post'[\w\-]+)",                     // '/post-slug'
    'home'      => "/"                                      // '/'
);

Next is to prepare the request uri.

$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );

Now that we have the request uri, the final step is to test uri on regular expression rules.

foreach ( $rules as $action => $rule ) {
    if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
        /* now you know the action and parameters so you can 
         * include appropriate template file ( or proceed in some other way )
         */
    }
}

Successful match will, since we use named subpatterns in regex, fill the $params array almost the same as PHP fills the $_GET array. However, when using a dynamic url, $_GET array is populated without any checks of the parameters.

    /picture/some+text/51

    Array
    (
        [0] => /picture/some text/51
        [text] => some text
        [1] => some text
        [id] => 51
        [2] => 51
    )

    picture.php?text=some+text&id=51

    Array
    (
        [text] => some text
        [id] => 51
    )

These few lines of code and a basic knowing of regular expressions is enough to start building a solid routing system.

Complete source

define( 'INCLUDE_DIR', dirname( __FILE__ ) . '/inc/' );

$rules = array( 
    'picture'   => "/picture/(?'text'[^/]+)/(?'id'\d+)",    // '/picture/some-text/51'
    'album'     => "/album/(?'album'[\w\-]+)",              // '/album/album-slug'
    'category'  => "/category/(?'category'[\w\-]+)",        // '/category/category-slug'
    'page'      => "/page/(?'page'about|contact)",          // '/page/about', '/page/contact'
    'post'      => "/(?'post'[\w\-]+)",                     // '/post-slug'
    'home'      => "/"                                      // '/'
);

$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );

foreach ( $rules as $action => $rule ) {
    if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
        /* now you know the action and parameters so you can 
         * include appropriate template file ( or proceed in some other way )
         */
        include( INCLUDE_DIR . $action . '.php' );

        // exit to avoid the 404 message 
        exit();
    }
}

// nothing is found so handle the 404 error
include( INCLUDE_DIR . '404.php' );

Solution 3

this is an .htaccess file that forward almost all to index.php

# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_URI} !-l
RewriteCond %{REQUEST_FILENAME} !\.(ico|css|png|jpg|gif|js)$ [NC]
# otherwise forward it to index.php
RewriteRule . index.php

then is up to you parse $_SERVER["REQUEST_URI"] and route to picture.php or whatever

Solution 4

PHP is not what you are looking for, check out mod_rewrite

Solution 5

Although already answered, and author's intent is to create a front controller type app but I am posting literal rule for problem asked. if someone having the problem for same.

RewriteEngine On
RewriteRule ^([^/]+)/([^/]+)/([\d]+)$ $1?id=$3 [L]

Above should work for url picture.php/Some-text-goes-here/51. without using a index.php as a redirect app.

Share:
286,316

Related videos on Youtube

Jazerix
Author by

Jazerix

I'm a happy guy, studying computer science at the University of Southern Denmark. I feel most at home working with C# or PHP -> Laravel. Lately I've also taken an interest in Vue.js.

Updated on February 20, 2020

Comments

  • Jazerix
    Jazerix about 4 years

    I have a URL that looks like:

    url.com/picture.php?id=51
    

    How would I go about converting that URL to:

    picture.php/Some-text-goes-here/51
    

    I think WordPress does the same.

    How do I go about making friendly URLs in PHP?

    • user123_456
      user123_456 almost 11 years
      You need to tweak a lot in the configuration of the Apache files...give an example of what you actually need? How do you insert this text? is it predefined or it it created dynamically? give as much info as you can to get right answer...and yea url could be a bit more descriptive :)
    • nietonfir
      nietonfir almost 11 years
      Why PHP? Apache's mod_rewrite is all you need for this and there are a lot of questions regarding that here on SO. And if you "think" that wordpress does the same, why don't you simply look it up? ;)
    • Admin
      Admin almost 11 years
    • adeneo
      adeneo almost 11 years
      Why are you doing this? Is the goal rewriting the URL's, if so use .htaccess or something similar. If the goal is just to change the string, $_GET only gets the querystring ?
    • Jazerix
      Jazerix almost 11 years
      The text would be the title of an image and then the id after the slash :) I'll have to look into what apache's mod_rewrite has to offer. Hope it's not too difficult :D
    • Revent
      Revent almost 11 years
      Here is a good tutorial on URL rewriting: addedbytes.com/articles/for-beginners/…
    • Amit Verma
      Amit Verma almost 3 years
      PHP can not be used to rewrite URLs . With PHP you can just redirect a particular page . Apache mod-rewrite is used to rewrite and change the appearance of URLs. If you want to learn htaccess and mod-rewrite you can just follow this simple and easy tutorial : helponnet.com/2021/04/15/htaccess-tutorial-for-beginers
  • nietonfir
    nietonfir almost 11 years
    @Jazerix This depends on the rules you define.
  • Niels Keurentjes
    Niels Keurentjes almost 11 years
    Apache introduced the FallbackResource directive a few major versions ago, which is now the preferred way to implement this behaviour at a lower performance cost since it doesn't need to launch the entire rewriting engine. Documentation here. Your rules are also flawed because you do not reference directories (!-d) and all the extension filters are obsolete - the -f should already catch them.
  • Mike
    Mike almost 11 years
    Instead of hard-coding it, you could just use regex to ignore the string completely. In other words, the only thing that counts is the ID part. Going to picture.php/invalid-text/51 would also redirect to the same location. You could also add a check to see if the string is correct and if not, redirect again to the correct location. That's how I did it on one of my sites using htaccess.
  • Niels Keurentjes
    Niels Keurentjes almost 11 years
    Convenient for smaller sites, but not really practical if you have to parse /blog/25 as well as /picture/51 and /download/684. Also, it's considered very bad practice (and gets you Google PR penalized!) if not all randomly generated URLs properly return 404.
  • Jack James
    Jack James about 10 years
    on my system at least, that was FallbackResource /index.php (note the leading slash)
  • Niels Keurentjes
    Niels Keurentjes almost 10 years
    @olli: the bad practice comment specifically refers to "not returning 404's for non-existent URLs", which is solved by the solution in the answer itself. As for the first question - FallbackResource only kicks in for files that do not actually exists on the filesystem, hence the fallback. So if you have a file /static/styles.css and refer to it as http://mydomain.tld/static/styles.css the code is never executed, making it work as expected and intended transparently.
  • andrebruton
    andrebruton almost 10 years
    How do you read the parameters? It does not work with $post_id = htmlentities($_GET['post']);
  • fireinspace
    fireinspace about 8 years
    you should use if($elements[0] === NULL) instead, as $elements will still return a count of one, even if it's empty.
  • 4 Leave Cover
    4 Leave Cover over 6 years
    @Danijel Can I have a complete source code? I tried the code above but only text was output, CSS no effect. Thank you.
  • jasinth premkumar
    jasinth premkumar about 6 years
    what is the + sign and $denotes in here /([0-9]+)$ if i have to do this in index page should i replace page name with index?
  • questionbank
    questionbank over 4 years
    I need to know what URL we have to write in href? because I am getting 404
  • Clout Hack
    Clout Hack over 2 years
    Awesome solution thank you!