Fun with Rewrite rules and Zend Framework

I ran into a problem with Apache’s mod_rewrite module this afternoon while setting up a Zend Framework web application. I had just setup a virtual host for my app and was setting up rewrite rules to forward requests to ZF’s front controller. However, I wanted requests for existing filesystem resources (such as CSS, JS, and image files) to be served directly by Apache, bypassing the Zend Framework.

 

Loosely following the ZF Quick Start Guide, I copied these rules into my VirtualHost configuration:


    ...
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} -s [OR]
    RewriteCond %{REQUEST_FILENAME} -l [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^.*$ - [NC,L]
    RewriteRule ^.*$ /index.php [NC,L]
    ...

These rewrite rules didn’t work for me. They basically state, “If the requested file exists and is greater than 0 bytes, or if the requested file is a symbolic link, or if the file is a directory, then don’t rewrite the http request and don’t process any more rewrite conditions or rules for this http request. Otherwise, forward the http request to /index.php”. The logic seemed correct, but requests for existing files were still being forwarded to the ZF front controller (/index.php).

To troubleshoot the problem, I enabled RewriteLog and traced its output. I started at log level 1 and worked my way up to level 4, where I finally found the problem. You can enable the RewriteLog by adding these lines to your VirtualHost configuration:

RewriteLog      /var/log/apache2/dev-rewrite.log
RewriteLogLevel 4

The following is the RewriteLog output for one http request:

[][][] (2) init rewrite engine with requested uri /tests/info.php
[][][] (3) applying pattern '^.*$' to uri '/tests/info.php'
[][][] (4) RewriteCond: input='/tests/info.php' pattern='-s' => not-matched
[][][] (4) RewriteCond: input='/tests/info.php' pattern='-l' => not-matched
[][][] (4) RewriteCond: input='/tests/info.php' pattern='-d' => not-matched
[][][] (3) applying pattern '^.*$' to uri '/tests/info.php'
[][][] (2) rewrite '/tests/info.php' -> '/index.php'
[][][] (2) local path result: /index.php
[][][] (2) prefixed with document_root to /www/home/velissimo/web/index.php
[][][] (1) go-ahead with /www/home/velissimo/web/index.php [OK]

The problem became apparent in the three lines printed for log level (4): mod_write was checking if the file “/tests/info.php” existed. That path was incomplete, missing the DocumentRoot prefix (“/www/dev/web/”). The path I expected mod_rewrite to check was: /www/dev/web/tests/info.php

And so I learned this lesson about mod_write:

In VirtualHost context, %{REQUEST_FILENAME} does not expand to the full path of the requested resource as the docs indicate. Instead, it expands to the path of the resource relative to the VirtualHost’s DocumentRoot. This might be a bug, since the Apache documentation is pretty clear that %{REQUEST_FILENAME} should expand to “The full local filesystem path to the file or script matching the request.”

Interestingly, %{REQUEST_FILENAME} expands to the full path when used in .htaccess context.

I should point out that the Zend Framework Quick Start Guide’s suggested rewrite rules were correct. The rules failed for me because I used them in VirtualHost context, while the ZF Guide called for them to be used in .htaccess context. When I moved the rules from my virtual host configuration to an .htaccess file, the rewrites worked correctly.

Still, I preferred to define the rules in the virtual host configuration. I was able to achieve this by changing the rewrite rules to:


    ...
    RewriteEngine   on
    # The leading %{DOCUMENT_ROOT} is necessary when used in VirtualHost context
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -s [OR]
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -l [OR]
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
    RewriteRule ^.*$ - [NC,L]

    RewriteRule ^.*$ /index.php [NC,L]

These rules worked for me on:

  • Apache 2.2.9 (single architecture 32 bit binary) running on OSX 10.5.6
  • Apache 2.0.54 (64 bit binary) running on Red Hat Enterprise Linux ES release 4.

I hope this helps save you some time or shed light on why Zend Framework’s rewrite rules aren’t working for you.

5 thoughts on “Fun with Rewrite rules and Zend Framework”

  1. I have a problem with rewriting:

    This link doesn’t works (404 error – not found)
    host/controller

    But this link works well:
    host/index.php/controller

    I can’t figure out what is the problem?

  2. Thank you so much for this!

    It is a nasty little bug. I guess the workaround will actually break things when Apache fix it, but then I suppose we should always test against upgrades before putting them live anyway. I’ve have added a BIG clear note about this in my config. Thanks again.

  3. It is a nasty little bug. I guess the workaround will actually break things when Apache fix it, but then I suppose we should always test against upgrades before putting them live anyway. I’ve have added a BIG clear note about this in my config. Thanks again.
    +1

Leave a Reply

Your email address will not be published. Required fields are marked *