2

Apache 2.4.10 on Windows. I have a stable Zend Framework 1 PHP application. I just decided to add an ErrorDocument directive to .htaccess. I'm completely baffled by the three different behaviors I get with 404's, depending on how they're triggered.

My .htaccess file contains:

ErrorDocument 404 "Page not found"
RedirectMatch 404 /uploads/(.*/)?private/

If I simply type a url for a non-existent page in the address bar, I get the error page produced by Zend Framework's ErrorController.

If I enter /uploads/private/foo (matching the RedirectMatch), then I get the ErrorDocument output, as expected.

But if in my PHP code I do

http_response_code(404);
exit(0);

then I get the browser's standard error page. This is the one I totally fail to understand. If I use the Network tab in Chrome Developer tools, I can see that all three requests give a 404 status. I guess I can imagine that the Zend Framework's error handling setup would trap the non-existent url, and do its thing, bypassing the ErrorDocument processing. But I don't understand how an immediate exit from PHP code with a status of 404 would get past the ErrorDocument.

What PHP code could I write to trigger the ErrorDocument directive?

EDIT:

Based on the answer from Tim Fountain, I should provide more context: As noted above, my ZF ErrorController page is set up to look the way I want - I see that page when I enter a URL for a non-existent page. In this particular situation, I'm actually running code from a WordPress mu-plugin (WP is installed below the ZF public directory). The .htaccess file naturally does not direct requests for existing files below public to ZF. Requests for anything below public/wp instead go to public/wp/index.php The mu-plugin bootstraps ZF (but does not call the bootstrap's run() method) and then examines the request. If it's not permitted (according to ZF's Auth session variables), I want to give a 404 (not 403). Previously, I did this by actually redirecting to a non-existent URL, and that showed the ErrorController page just fine - but it also left the address bar displaying the non-existent URL. I decided to add the ErrorDocument directive so that the address bar would still show the URL typed by the user. In my actual code, I did not have a simple string on the directive, but rather this URL: /default/error/error?error_handler[type]=404. I changed it to a simple string just to make sure there wasn't something about the URL causing a problem. Note that when I enter a URL for something within a private directory triggering the RedirectMatch, I do see the ZF ErrorController page, so I know that the URL in itself is not a problem. I just wanted to reduce the complexity for this posting.

Tim's answer says that my problem code is sending an empty 404 page directly to the browser, and the browser is changing it to explanatory text. That's clearly what's happening!

Does this mean my problem is that I need to produce some non-zero-length page content and use header() to set the status, because http_response_code() sends empty content directly to the browser? But what's unclear in his answer is why the ErrorDocument directive would be bypassed.

The request URL causing this behavior is http://www.example.com/wp/foo. The DocumentRoot is .../public. This is not rewritten by .htaccess to go to ZF, but goes directly to public/wp/index.php. In which case I would have thought that the ErrorDocument would be triggered...

EDIT 2:

Or wait, maybe Tim's point isn't really about ZF Error handling, but that from Apache's point of view, the incoming request was satisfied (by going to public/wp/index.php and running PHP), so it's up to the PHP code to handle it, I can't get back to Apache's ErrorDocument after that! I guess I had a really fundamental misunderstanding of how ErrorDocument works. I need to figure out some way to invoke the ErrorController code directly to display the page I want with a 404 status! Duh!

I guess I'll accept Tim's answer, though if he wants to clarify/generalize his point about when ErrorDocument processing comes into play, that would be nice :-)

EDIT 3:

Just to wrap this up. I accepted Tim's answer because it revealed my fundamental misunderstanding of how the Apache ErrorDocument directive works. But it didn't directly show me how to solve the problem I was experiencing, with discovering that I wanted to return a 404 status using my Zend Framework custom status page, but I was running in a WordPress query having bootstrapped ZF without having started the MVC application running under the bootstrap. I had no experience mucking around with ZFs front controller, but after digging into the documents a bit, I came up with this solution, which has the exact same effect as the full ErrorDocument directive I had wanted to trip.

$error_url = $this->serverUrl() . '/default/error/error?error_handler[type]=404';
$request = new Zend_Controller_Request_Http($error_url);
$front = Zend_Controller_Front::getInstance();
$front->returnResponse(true);
$response = $front->dispatch($request);
$response->setRawHeader('HTTP/1.1 404 Not Found');
$response->sendResponse();
exit(0);
2

Presumably in your htaccess rules you also have a rewrite rule to route all requests to Zend Framework. At this point, Apache (and ErrorDocument) isn't really in the picture, as you've told it you want the requests to be handled by PHP.

When you do:

http_response_code(404);
exit(0);

you're sending an empty 404 page to the browser. Many browsers are setup to show their own user friendly 404 page if the size of the 404 page they receive from the server is less than X bytes. (Just for a better user experience.)

Zend Framework should catch this and use its own error page depending on where you put that PHP code (and possibly based on some configuration options).

I'd suggest you update the Zend Framework ErrorController page to look as you want. I don't believe there's a way to hand processing of the request back to Apache (as it were).

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.