Moving project code outside of webroot (plus multiple project support)

  1. Make symbolic links to the Yii framework code.
  2. Move protected/ out of the webroot
  3. Move runtime/ out of protected/
  4. Resulting structure
  5. Config file changes
  6. Permissions

Yii's by-default directory organization works well enough, but there are several steps one can take that improve the security and serviceability of the system, especially in the context of multiple Yii projects on the same machine (including multiple versions of the same project).

In this article we'll use three versions of the same blog project: production, test, and development, and presumably changes migrate from the last to the first. The precise top-level folder doesn't matter, and though this example uses /var/www/, it could be anywhere else.

  • /var/www/blog/
  • /var/www/blog-test/
  • /var/www/blog-dev/

These instructions were written assuming Yii 1.1 on a Linux server with Apache.

Make symbolic links to the Yii framework code.

Since the Yii framework can be shared among projects, our normal practice is to put the Yii code up at the top level with the full version information in the directory name.

  • /var/www/yii-1.1.4.r2429/
  • /var/www/yii-1.1.5.r2654/
  • ...

and then symbolic link from within the top-level of the project:

# cd /var/www/blog
# ln -s /var/www/yii-1.1.5-r2654 yii

Now, /var/www/blog/yii/ refers to the proper framework, and this is a fixed relative position within the particular project.

This also makes it easy to upgrade just one project at at time by changing the symbolic link.

Move protected/ out of the webroot

Nothing in the protected/ folder needs to be fetched directly by a webuser, so it's a needless security risk to put where it has to be protected with mechanisms that are easy to get wrong (.htaccess): this is just asking for trouble with no upside.

Instead, move the protected/ folder to the same level as the webroot/ folder, so there is no possibility of accessing it via the browser.

Move runtime/ out of protected/

The protected/ folder (even after moving) is composed almost entirely of source code, except for the runtime/ folder, which is used by Yii for logs, state information, and the like. Though it's reasonable that Yii have such an area, it just doesn't really belong with the source code.

So my practice is to move it out of protected/ as well, up at the same level as the webroot.

Resulting structure

After making these changes, the Yii directory structure looks like:

/var/www/blog/webroot/...
/var/www/blog/webroot/css/...
/var/www/blog/webroot/assets/...
/var/www/blog/protected/...
/var/www/blog/runtime/...
/var/www/blog/yii  --> symlink to /var/www/yii-1.1.5.r2654

The protected/ folder is now composed entirely of source code, is invisible to the web user, and the runtime/ area is separate from everything else.

Config file changes

We certainly have to tell Yii where these parts are, and this involves editing both the main config file, plus the application entry script.

In the config file we should document well what we're doing (to make it easier for others to follow), and a small helper function assists by joining paths together and removing spurious /./ and /../ components. Though we don't currently use the $webrootPath variable, adding it anyway helps give a fuller context to where all the parts fit together.

NOTE - some have suggested using the DIRECTORY_SEPARATOR constant instead of literal /, but it's not clear that this is really necessary even on a Windows machine. Those who find a case where it really does matter are encouraged to Edit this page to add your findings.

// in protected/config/main.php
...
// Set up path variable to reflect the new directory structure
//   $WEBHOME/             -- $homePath
//   $WEBHOME/webroot/     -- $webrootPath
//   $WEBHOME/protected/   -- $protectedPath
//   $WEBHOME/runtime/     -- $runtimePath

function _joinpath($dir1, $dir2) {
    return realpath($dir1 . '/' . $dir2);
}

$homePath      = dirname(__FILE__) . '/../..';
$protectedPath = _joinpath($homePath, 'protected');
$webrootPath   = _joinpath($homePath, 'webroot');
$runtimePath   = _joinpath($homePath, 'runtime');

return array(
    'basePath'    => $protectedPath,
    'runtimePath' => $runtimePath,
    ...
);

These variables may be used in other places as well, such as configuring the location of the authFile for CPhpAuthManager

The entry script in webroot/index.php must be taught where to find both the protected/ folder as well as the Yii framework, and it's easy to make these modifications:

...
$yii    = dirname(__FILE__) . '/../yii/framework/yii.php';
$config = dirname(__FILE__) . '/../protected/config/main.php';
...

Because these paths are used only once, there's no real need to optimize with the realpath() function as was done in the configuration file itself.

NOTE - if you have console-mode programs, you'll need to make similar $yii and $config changes in that entry script as well, though the exact form of the path changes depends on where your entry scripts are located.

Permissions

After making all these structural changes, do ensure that the key folders have the same permissions as before. In particular, two folders must be writable by the webserver user, and this is achieved with a combination of user, group, and permissions:

  • webroot/assets/
  • runtime/
21 1
19 followers
Viewed: 52 649 times
Version: 1.1
Category: How-tos
Written by: Steve Friedl
Last updated by: Steve Friedl
Created on: Dec 12, 2010
Last updated: 6 years ago
Update Article

Revisions

View all history

Related Articles