1

I have a website with the following structure

index.php
contact.php
about.html
forum(dir)
   - index.php
   - thread.php

What i want my htaccess to do

  • Remove .php & .html extension, eg: when you visit contact page the url should be www.domain.com/contact/ & www.domain.com/about/
  • When the user visits the forum the url should be www.domain.com/forum/thread/10004000/ instead of www.domain.com/forum/thread?id=10004000

I'm not sure how to figure it out. I was able to remove the extensions but failed in the / "forward slash" part and the ?id=10004000

This is my .htaccess part

RewriteEngine on 

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html [NC,L]

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.php -f
RewriteRule ^(.*)$ $1.php [NC,L]
2

To my mind, there are two ways to approach this problem. The first way is what you're already doing, i.e., trying to do everything with mod_rewrite. This is totally fine for small, simple projects but if your project grows in size or complexity, it can become unwieldy to develop and maintain. The second option is to have your application manage the rewrites, in your case likely using PHP. Here are working examples of both approaches to help get you started.

Approach 1: Complex mod_rewrite

With this approach, you need an addition to your .htaccess (or httpd.conf) and two modifications to get it working the way you want, i.e., rewriting IDs for forum posts and allowing trailing slashes in other urls. Here is an annotated working version of an .htaccess that you might use:

# Make sure the rewrite engine is on
RewriteEngine On

### Rewrite forum IDs ###
# Make sure that the request isn't an existing directory
RewriteCond %{REQUEST_FILENAME} !-d 
# Make sure that the request isn't an existing file
RewriteCond %{REQUEST_FILENAME} !-f 
# Matches urls that begin with "forum/" and that have some additional text after that.
# It then gets anything after the "forum/" (that isn't a forward slash) and appends that string as the ID
RewriteRule ^forum/([^/]*) /forum/thread.php?id=$1 [NC,L] 

### Rewrite for HTML files
# Make sure that the request isn't an existing directory
RewriteCond %{REQUEST_FILENAME} !-d 
# If the requested filename with html appended to it exists, we'll use it
RewriteCond %{REQUEST_FILENAME}\.html -f 
# Rewrite everything but the trailing slash
RewriteRule ^([^/]*) $1.html [NC,L] 

### Rewrite for PHP files
# Make sure that the request isn't an existing directory
RewriteCond %{REQUEST_FILENAME} !-d 
# If the requested filename with html appended to it exists, we'll use it
RewriteCond %{REQUEST_FILENAME}\.php -f 
# Rewrite everything but the trailing slash
RewriteRule ^([^/]*) $1.php [NC,L] 

To solve your trailing slash problem, the key change I made was changing ^(.*)$ to ^([^/]*) in your rewrite rules -- which matches all characters except slashes. Note that in the regex the first caret (^) matches against the beginning of a string, but the second caret ([^/]) means not whatever is following it, in this case a forward slash. (More on this can be found at https://stackoverflow.com/a/16944517/14316089)

Adding ID to the thread is a pretty simple match looking for anything after "/forum/" in the url and appending it to "forum.php?id=". If you need further help with this, Apache's mod_rewrite documentation is really good and (I think) readable. There's a good intro at https://httpd.apache.org/docs/2.4/rewrite/intro.html.

Approach 2: Routing within your application

The second approach is to use very basic mod_rewrite code and to have your web application handle the complexities involved in routing your users' requests. It's perhaps a little more complex to set up, but I think it's much more flexible and powerful -- and in the long run far, far simpler to develop and maintain.

To do this, you'll have a very simple .htaccess file (or httpd.conf) that basically rewrites everything to a single file that will then handle your requests. Your .htaccess file might be something like this:

# Make sure the rewrite engine is on
RewriteEngine On

# Make sure that the request isn't an existing directory
RewriteCond %{REQUEST_FILENAME} !-d 
# Make sure that the request isn't an existing file
RewriteCond %{REQUEST_FILENAME} !-f 
# Rewrite everything to index.php!
RewriteRule ^.*$ index.php [L]

Next you need your application to route your requests appropriately. In this case, I created a PHP class that I called Router that does this. Here's my file:

<?php
// Change this to wherever your application files are. You might even move those files out of the document root
define("APP_PATH", "/path/to/your/application/");

/**
 * This is the sample router class. You'll almost certainly want to modify it a ton, but it should at least
 * provide a proof of concept to get you started
 */
class Router 
{
    /**
     * This variable holds a singleton instance of this class
     * @var Router
     */
    protected static $instance;

    /**
     * Give __counstruct protected visibility so that the class can only be instantiated with the getInstance() method
     */
    protected function __construct() {}

    /**
     * @return Router
     */
    public static function getInstance()
    {
        if (Router::$instance === null) {
            Router::$instance = new Router();
        }
        return Router::$instance;
    }

    /**
     * Some sample logic of how the router will parse and process the request URIs
     */
    public function run()
    {
        $parsedUri = $this->parseRequestUri();

        if (isset($parsedUri[1])) {
            // Matches requests for .html files
            if (file_exists(APP_PATH . $parsedUri[1] . '.html')) {
                include APP_PATH . 'about.html';               
            // Matches requests for .php files
            } elseif (file_exists(APP_PATH . $parsedUri[1] . '.php')) {
                include APP_PATH . $parsedUri[1] . '.php';
            // Matches forum threads
            } elseif ($parsedUri[1] == 'forum') {
                if (isset($parsedUri[2])) {
                    $_GET['id'] = $parsedUri[2];
                    include APP_PATH . 'forum/thread.php';
                } else {
                    include APP_PATH . 'forum/index.php';
                }
            // If nothing matches throw a 404 error.
            } else {
                http_response_code(404);
                echo "<h1>404</h1>";
            }
        }
    }

    /**
     * Parses the REQUEST_URI
     */
    protected function parseRequestUri()
    {
        $urlPath = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        return explode('/', $urlPath);
    }
}

// Get a router instance and run it
$router = Router::getInstance();
$router->run();

While this file should do everything you want it to do, you should obviously modify it as necessary. But because the logic is now in PHP and not in mod_rewrite, you have access to a whole bunch of language constructs that you wouldn't otherwise if you were just using mod_rewrite. Plus (I think) PHP is a lot easier to debug than mod_rewrite.

(Bonus) Approach 3: Use an existing framework

As a special bonus, I'm including another approach: use an existing PHP framework and have it handle the routing for you. Any modern framework worth its salt allows you to manage and route the URLs you want to see easily. You'd almost certainly have to modify your existing application to have it work within that framework, but you'll gain a lot of functionality and reap the benefits of the frameworks team essentially writing code for you, adding new features, plugging security holes and all that other good stuff. Frameworks also constrain you in a good way in that the rules and methods you need to follow to use the frameworks force you to write better code.

Anyway, lots of ways to do what you want. Hopefully this gets you off on the right path.

1
  • 1
    Thank you for helping me out. I think using the 2nd approach what you mentioned really helps well! – user14591460 Nov 8 '20 at 7:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.