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
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:
(I had to slightly change the original code to prevent infinite looping. That’s why I added the
END
flag to the secondRewriteRule
.)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: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:
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:
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.