Pragmatism in the real world

Zend Framework on a shared host

When you deploy a Zend Framework website to a shared host, you usually cannot change the DocumentRoot to point at the public/ folder of the website. As a result the URL to the website is now http://www.example.com/public/. This doesn’t look very professional, so we’d like to remove it.

The easiest way, given a ZF project created using Zend_Tool is this:

Create /index.php


< ?php define('RUNNING_FROM_ROOT', true); include 'public/index.php';

This uses the index.php already created by Zend_Tool and means that we don't have to change anything if we move to a VPS host where we can set the DocumentRoot directly to public/.

Create /.htaccess


SetEnv APPLICATION_ENV development

RewriteEngine On
RewriteRule .* index.php

We create a .htaccess file that redirects every request to index.php. We want to do this so that no one can try and read application/configs/application.ini. Obviously, set the APPLICATION_ENV to the correct value!

Referencing public facing files

Having created a very aggressive, rewrite rule, what about CSS/JS/image files though?

Fortunately, we already have a .htaccess file in the public/ folder that correctly handles this situation. As Apache will execute the .htaccess files in the deepest directory it finds, any reference to a public facing file within the public/ folder will correctly be served.

You do have to be aware of this when referencing public facing files though and add the /public to the baseUrl yourself.

For example, you may set up your CSS file and other view settings within a Front Controller plugin like this:


class App_Controller_Plugin_View extends Zend_Controller_Plugin_Abstract
{
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
$frontController = Zend_Controller_Front::getInstance();
$view = $frontController->getParam('bootstrap')->getResource('view');

$view->doctype('XHTML1_STRICT');

$baseUrl = $request->getBaseUrl();
if (defined('RUNNING_FROM_ROOT')) {
$baseUrl .= '/public';
$frontController->setBaseUrl($baseUrl);
}
$view->headLink()->appendStylesheet($baseUrl . '/css/main.css');
$view->headLink()->appendStylesheet($baseUrl . '/css/screen.css', 'screen');
$view->headLink()->appendStylesheet($baseUrl . '/css/print.css', 'print');
}
}

(This code assumes you have added a resources.view[] = "" to your application.ini)

As we have a constant that tells us if we’re running from the root, we can dynamically add the /public into the URL. If we change to a host where the DocumentRoot is set directly to the public/ folder, then we don’t need to change our code.

That’s it. Your Zend Framework application works nicely with shared hosts.

Update:
Thomas Dutrion has translated this post into French at http://blog.generation-pc.net/2011/04/zend-framework-sur-un-hebergement-mutualise/. Thank you, Thomas!

46 thoughts on “Zend Framework on a shared host

  1. Where is $request defined because in a line you have $baseUrl = $request->getBaseUrl();
    but $request doesn't exist at that moment.

    Thanks!!

  2. Fernando,

    $request is passed in as a parameter to dispatchLoopStartup()

    Lorenzo,

    That's an nice solution :)

    Regards,

    Rob…

  3. Rob,

    What I mean is, whatever the public folder of the hosts is, so for example in your actual zf app, name the "public" folder, public_html or httpdocs, etc, instead of going with the name public, which is the default.

    Steve

  4. Rob,

    Thanks for the clarification. Not all shared hosts impose this restriction. We use Heart Internet at work and we can store the application folders, etc, above public_html.

    Good to know, thanks!

    Steve

  5. Great tutorial. Not all hosts have this restriction though. Most hosts that I know use cPanel which allows you to store the application folders in the main directory above public_html.

    Most hosts using cPanel don't change the name of the document root and leave the default on.

  6. My shared host does not have this problem, but it does not over memcache or similar. This means that Zend_Cache does not work as well.

    Can you write a post about your point of view of using Zend_Cache on a shared host without MemCache?

  7. I think, the simplest solution is using the following .htaccess file in ROOT directory.

    RewriteEngine On
    RewriteRule ^(.*)$ public/$1 [L,QSA]

  8. @Mary, if your hosting provider does not offer memcached or apc, you could use the file system for caching.

    You can choose the 'File' backend when instantiating the cache object.

  9. Nice post.

    Although I would say that htaccess can be disabled as well – not to mention that they are a performance hit (over apache conf files).

    Just a quick question – do you know how to make zend tool use something other than public? i.e. I want to use public_html, or "web" (i.e. something other, so that I can use existing web roots, etc.) – other than symlinking the directories….?

    Thanks.

  10. ZF doesn't care, I know that – but using zend tool – create a project, it names it public – how do you change the defaults on that? It's probably obvious, but I can't seem to find it.

    Cheers.

  11. Awesome solution! I really like the other two posted by Lorenzo Alberton and Michal Lasak. I came up with my own solution to this one time using only mod_rewrite rules but it was nowhere as elegant as Michal's solution. Thanks for the article Rob and thanks everyone else for the comments.

  12. I just wanted to add that on GoDaddy and maybe some other shared hosts you need to add to the top of the .htaccess file generated by Zend_Tool the following:

    Options -MultiViews

    I'm not sure why this is needed; if someone else knows please explain.

  13. hi
    did anybody tried this approach on godaddy?
    i have a site ready to be uploaded to my domain on godaddy and i`m not sure how to do it.

    i will appreciate any detailed help.

    best regards

  14. Hi Rob very useful solution for shared hosting. I modified the plugin a bit:


    class App_Plugin_BaseUrl extends Zend_Controller_Plugin_Abstract
    {
    public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
    {
    $frontController = Zend_Controller_Front::getInstance();
    $baseUrl = $request->getBaseUrl();
    if (defined('RUNNING_FROM_ROOT')) {
    $baseUrl .= '/public';
    $frontController->setBaseUrl($baseUrl);
    }

    }
    }

  15. hi

    i get the css files and images but all links are not working.

    i have this link: <a href="/bloging/css/index"
    it works great on my local site but on the live site it does not work. it just sends me back to index, even though the url say the right url.

    what am i missing here? please help

  16. just hosted my site built on ZF yesterday on godaddy … I did fear this issue as i had never done the ZF setup on a shared server earlier and i did not find this article

    so i tried out something different that worked

    My folder structure is now
    public/
    application/
    cache/
    library/
    images/
    css/
    .
    .
    .
    and edited this in my index.php

    set_include_path(implode(PATH_SEPARATOR, array(
    realpath(dirname(__FILE__) . '/library'),
    get_include_path(),
    )));

    // Define path to application directory
    defined('APPLICATION_PATH')
    || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/application'));

    This worked for me and my site is live and running. Let me know if there are any problems with this folder structure

  17. And what about Rob's solution: what if you need to give access to some files in the site's root, robots.txt for example? I propose to use these statements in .htaccess:

    RewriteCond %{REQUEST_URI} /$
    RewriteCond %{REQUEST_FILENAME} -f
    RewriteRule .* – [L]

    Obviously, you need to protect some of the files in the root

  18. Excuse me for a typo in last comment.
    .htaccess:

    RewriteCond %{REQUEST_URI} ^/[^/]+$
    RewriteCond %{REQUEST_FILENAME} -f
    RewriteRule .* – [L]

    First statement checks whether URI is like:
    ["/" (root)] + [anything except "/" (any filename)]

  19. Ive been wrestling with this for a few days now, perhaps you can help.

    1. I am using shared hosting.

    This is my directory structure.

    public/
    application/
    library/
    css/
    images/
    js/
    index.php

    I am trying to get the site to function with the following .htaccess

    RewriteEngine on

    RewriteRule !.(js|ico|gif|jpg|png|css|swf)$ index.php
    Options -Indexes

    Nothing fancy but I get the following error 404

    The requested URL /about/ was not found on this server.

    the main page shows up perfectly, but any other page doesn't work. However, if I create a folder called public/about/index.html the page is rendered.

    How can I get the about page to work with the .htacess ans still keep my application in tact?

  20. Rob,

    Hi i'm new to ZF, now where should i write your plugin class? Is it under subfolder of Application or i will make new folder as plugin?

    Then where do i call it? is it in the index.php file?

  21. Greffin,

    App_Controller_Plugin_View lives in lib/App/Controller/Plugin/View.php

    you need this in application.ini:
    autoloadernamespaces[] = "App_"

    Regards,

    Rob…

  22. Hi Rob, can you give me some example code for index.php that calls the plugins view.

    Thanks.

  23. Do you know of anyway with htaccess to disable someone from using your domain to point to their own website on the same server? Ex: they use YOURDOMAIN.com to promote their PHISHING WEBSITE.COM by using this simple URL to send users : YOURDOMAIN.COM/~phishing/file.html

    Any help would be greatly appreciated. Thanks

  24. Hey Rob,
    thanks for your solution.
    I just noticed that it breaks the redirector.

    So I refrained from giving the altered URL to the frontcontoller and applied a quick fix like the following:

    $frontController = Zend_Controller_Front::getInstance();
    $view = $frontController->getParam('bootstrap')->getResource('view');
    $baseUrl = $request->getBaseUrl();
    if (defined('RUNNING_FROM_ROOT')) {
    $baseUrl .= '/public';
    }
    $view->baseUrlVar = $baseUrl;

    … and than using $view->baseUrlVar in the view-scripts like that:

    baseUrlVar ?>/path/to/file

    But I am guessing that there's a way more elegant solution out there.

    Cheers, Bert

  25. Hmm, the purifier destroyed the last code segment, but I think, you get the drift.

  26. Hi,

    I am using the configuration and directory structure from your book ZFIA.

    I get the following error when using the above controller:

    Fatal error: Call to a member function getResource() on a non-object in /var/www/html/aotf/library/Places/Controller/Plugin/UrlSetup.php on line 7

    It may be that I do not see an application.ini file.

    Thus, I cannot do:

    "(This code assumes you have added a resources.view[] = "" to your application.ini)"

    Please advise. Thanks!

  27. Thank you so much for this post, it worked like a charm. My css and js file loads fine now. This is however one problem I can't seem to work around.

    This is what I am trying to do in my global.css file to load a background image when I am working on my localhost:

    background:url("/images/bg2.jpg")

    The above line will NOT work on shared host environment. The images folder resides in the public folder, which also has the index.php that starts everything.

    I have to change it to
    background:url("/public/images/bg2.jpg") in order for the image to load.

    Is there anyway to modify the .htaccess file so that
    background:url("/images/bg2.jpg") will work just like on my localhost.

    It would be nice if I don't have to keep 2 version of css files for localhost and remote server.

    Thanks.

    1. qin,

      I'd try to use relative URLs for the images in the CSS file. The URL should be relative to the location of the CSS file.

      Regards,

      Rob…

  28. Rob
    Thanks for the quick reply.

    I should have tried harder before posting next time :)

    I lost track of while moving these files around.

    Thanks,
    Qin

  29. Hi Rob,

    Your solution did solved the referencing public facing files. But what about the hyperlink created by something like this: <a href="url(array('controller'=>'index','action'=>'test'));?>">Test Page ?

    It seems the url did not include '/public/' in the above link.

    Please advise.

    Thanks
    James

Comments are closed.