Symfony – Remove .php From Controller using Symlink
Symfony is great, I get it, but there are certain situations where the seemingly easiest task may take days to uncover. If you are reading this post then chances are you are trying to figure out an elegant approach to removing the .php from your controllers. I had to jump down the same rabbit hole, and after days of research and scouring I finally got it working with minimal changes.
There are several posts on this subject via google searches, some using rewrite rules, others using the ‘no_script_name’ in settings.yml. I think you will find the following approach to be the best. There are two config set-ups; the first assumes your site is hosted via dedicated host, the second assumes you are using the .htaccess file packaged with symfony in the /web directory. This article was developed / tested using the dedicated hosting solution and has a high probability of working, the virtual host solution is an assumed theory and should work, but may need some minor tweaks.
Dedicated Hosting
If you are fortunate enough to have dedicated hosting, then you (or a sys admin) have access to the apache config files needed to make this approach work. This solution assumes you are using a virtual host config files, if you are not, you really should. Virtual host config files keep your primary httpd.conf file clean and uncluttered. I am not going to go into detail on how to setup virtual host config files here, but you can find many an article on the subject via google. The one thing you should know, is that when conf files are loaded they are loaded alphetically from the directory you specify in your httpd.conf file (something like: Include conf.d/*.conf).
What I did is create a file in the conf.d directory like 010_symfony.conf, so my dir looked like:
mpurcell@dev1 ~ $ -> ls /etc/httpd/conf/vhost.d/ total 16 drwxr-xr-x 2 mpurcell webdev 4096 Jun 9 07:43 . drwxr-xr-x 3 mpurcell webdev 4096 Jun 14 07:58 .. -rw-r--r-- 1 mpurcell webdev 0 Jun 1 10:36 000_global.conf -rw-r--r-- 1 mpurcell webdev 81 Jun 14 13:13 010_symfony.conf -rw-r--r-- 1 mpurcell webdev 1681 Jun 14 20:05 999_default.conf
As you can see from the dir listing, I have 3 conf files. The global file at this point is empty, it’s just a footprint if I ever need to add anything that needs to be considered before all other config files. The next file to be loaded is the 010_symfony.conf file which the contents look like:
mpurcell@dev1 ~ $ -> cat /etc/httpd/conf/vhost.d/010_symfony.conf
<FilesMatch "^(fe|be)?(_dev)?$">
ForceType application/x-httpd-php
</FilesMatch>
The Apache directive; ‘FilesMatch’ allows for regex matching of the URI. In the example above I am looking for any urls that have fe (frontend app) and be (backend app) along with their associated dev controllers (_dev). If there is a match, then the ForceType kicks in and tells Apache that the matching symlink should be treated as if it were an actual php file. Be sure to restart your apache instance for these changes to take effect.
Now that we have this directive in place, lets setup the necessary symlinks in the symfony /web directory.
cd /path/to/symfony/web ln -s fe.php fe ln -s fe_dev.php fe_dev ln -s be.php be ln -s be_dev.php be_dev
As you can see we created four symlinks, each one pointing to a specific controller. As mentioned above, ‘fe’ is short for ‘frontend’, and ‘be’ is short for ‘backend’. You can change these to whatever you like, if you would rather use ‘admin’ simply create the symlink then update the /etc/httpd/conf/vhost.d/010_symfony.conf to look like:
<FilesMatch "^(fe|be|admin)?(_dev)?$">
ForceType application/x-httpd-php
</FilesMatch>
Notice that I added ‘admin’ to the FilesMatch regex, so now if you have a symlink of ‘admin’ pointing to be.php, it will work. Remember this is OPTIONAL and simply demonstrates the flexibility of this overall symlink approach for removing .php from your controllers.
Now, if you use one of your symlinked controllers you may notice an issue with the links symfony constructs. For me, they doubled up the controller name so my links looked something like: ‘/be_dev/be_dev/user/login’. This issue is where I spent most of my time resolving, I thought maybe it was an apache config setting, I had no idea it was under the symfony hood. To fix this issue you need to do the following:
vi /path/to/symfony/app//factories.yml
#If you don't already have the following activated you will need to make sure it's active under the 'all' directive
all:
...
request:
class: sfWebRequest #if you are using custom class be sure to replace this with your custom class name
param:
logging: %SF_LOGGING_ENABLED%
path_info_array: SERVER
path_info_key: PATH_INFO
relative_url_root: ""
...
The real magic lies in the ‘relative_url_root’ directive. According to symfony docs, it tells symfony to prefix all links etc with this value. So if you had your files in a /web/blog dir you could set this to /blog. In the solution discussed here, we are using symlinks in the root /web dir, so instead of setting relative_url_root to a value, we just nullify it. By nullifying relative_url_root this results in symonfy not doubling up the controller in subsequent links. So the issue described above where links resulted in /be_dev/be_dev/user/login, should now result in /be_dev/user/login, which is what we want. Also be sure that you make the above changes to all your apps factories.yml files so they behave the same way.
Now, clear symfony cache and try your url again, you should see your page behave as if you went through the native controller (be_dev.php). So http://yourhost.com/be_dev.php should behave exactly the same as http://yourhost.com/be_dev
Pretty cool right!?
Virtual Hosting
For the same behavior to work on a site which is virtually hosted, you simply need to add the aformentioned ‘FilesMatch’ directive to your /web/.htaccess file so that it looks something like:
<FilesMatch "^(fe|be)?(_dev)?$">
ForceType application/x-httpd-php
</FilesMatch>
# Settings below this line are symfony defaults no need to edit
Options +FollowSymLinks +ExecCGI
RewriteEngine On
# uncomment the following line, if you are having trouble
# getting no_script_name to work
#RewriteBase /
# we skip all files with .something
RewriteCond %{REQUEST_URI} \..+$
RewriteCond %{REQUEST_URI} !\.html$
RewriteRule .* - [L]
# we check if the .html version is here (caching)
RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
# no, so we redirect to our front web controller
RewriteRule ^(.*)$ index.php [QSA,L]
Closing
Hopefully the solutions provided above work for your and your specific circumstances, if not feel free to add a comment and we can see what the issue is. I feel that the solution presented is elegant and easy to implement no matter what your situation. Good luck!

Forgot to mention you need to disable ‘no_script_name’ in your apps//config/settings.yml file for production setting. For some reason it’s on by default.