skip to Main Content

I’d like to use mod_rewrite (in .htaccess) to map “clean” URLs to their corresponding php files in a subdirectory, for example:

https://example.com/test/
-->
https://example.com/p/test.php

My first step was to add trailing slashes to all URLs that lack one:

RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*[^/])$ $1/ [L,R=301]

Then I added the following lines to make sure the path/file doesn’t exist already:

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f

And finally the RewriteRule that maps the requested path to the corresponding file in the subdirectory /p/:

RewriteRule ^(.+)$ /p/$1.php

This seems to work fine—unless I’m requesting a URL that doesn’t exist. Then the operation will result in an endless loop such as:

https://example.com/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/p/test/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/.php/

I’ve already tried adding another RewriteCond right before the RewriteRule to ensure the requested file actually exists in the subfolder /p/, but my approach doesn’t work:

RewriteCond %{DOCUMENT_ROOT}/p/$1.php -f

Can anyone please tell me what I’ve been doing wrong?

2

Answers


  1. Chosen as BEST ANSWER

    In case anyone needs this: I’ve built upon MrWhite’s solution and added another RewriteRule that should prevent direct access to any .php files, including the ones in the /p/ subdirectory. Otherwise users would be able to access the same file under the “clean” URL and its direct path.

    The complete code looks like this:

    RewriteEngine on
    
    # Append trailing slash (if omitted)
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.+[^/])$ /$1/ [R=301,L]
    
    # Rewrite "/test/" to "/p/test.php" if it exists
    RewriteCond %{DOCUMENT_ROOT}/p/$1.php -f
    RewriteRule ^(.+)/$ p/$1.php [L,END]
    
    # Prevent direct access to php files
    RewriteRule .php - [R=404]
    

    (I had to slightly change the original code to prevent infinite looping. That’s why I added the END flag to the second RewriteRule.)


  2. I’ve already tried adding another RewriteCond right before the RewriteRule to ensure the requested file actually exists in the subfolder /p/, but my approach doesn’t work:

    Yes, you will need to do that… check that the file you intend to rewrite to actually exists, rather than rewriting anything that simply doesn’t exist (which will result in a rewrite loop if the target also does not exist). However, your "problem" is that your URLs end in a trailing slash and you don’t appear to be accounting for this in your rule. So, given a request for /test/ you are effectively trying to test for %{DOCUMENT_ROOT}/p/test/.php, which naturally does not exist.

    You need to exclude the trailing slash in the capturing subpattern in the RewriteRule pattern. For example:

    # Rewrite "/test/" to "/p/test.php" if it exists
    RewriteCond %{DOCUMENT_ROOT}/p/$1.php -f
    RewriteRule ^(.+)/$ p/$1.php [L]
    

    There’s no need for the additional filesystem checks (and overhead) to check that the request does not map to a file or directory – since these should be mutually exclusive events. (If you did happen to have a directory of the same name then the corresponding .php file would otherwise not be accessible.)

    Your complete file then becomes:

    RewriteEngine on
    
    # Append trailing slash (if omitted)
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.+[^/])$ /$1/ [R=301,L]
    
    # Rewrite "/test/" to "/p/test.php" if it exists
    RewriteCond %{DOCUMENT_ROOT}/p/$1.php -f
    RewriteRule ^(.+)/$ p/$1.php [L]
    

    Note that there is no need for the second condition in the rule that appends the trailing slash to test that the requested URL does not end in a trailing slash, since this is already handled by the RewriteRule pattern (the pattern simply would not match if there was a trailing slash).

    And no need for the RewriteBase directive here. On the external redirect it is better to be explicit and include the slash prefix on the substitution string.


    Aside:

    My first step was to add trailing slashes to all URLs that lack one:

    Just to clarify, all your internal links must have already been updated to include the trailing slash, otherwise your users (and search engines) will be unnecessarily redirected all the time.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search