Development vs. Productive Setup

If you want to deploy your cakephp app you usually have to change a few lines of code. But we want to minimize that.

My example setup:

  • development: Windows (>= Vista)
  • productive: Linux (Debian)

Database Setup

Create a library file and extend this from your DATABASE_CONFIG. This way you can let your app automatically select the correct database var at runtime.

class BASE_CONFIG {
    var $environments = array('default', 'sandbox', 'online');

    var $default = array();

    /**
     * switch between local and live site(s) automatically by domain
     * or manually by Configure::write('Environment.name')
     * 2009-05-29 ms
     */
    function __construct() {
        $environment = $this->getEnvironmentName();
        if ($environment && isset($this->{$environment})) {
            $this->default = array_merge($this->default, $this->{$environment});
        }
        $this->test = $this->default;
        $this->test['prefix'] = 'zzz_';
    }

 	function getEnvironmentName() {
        $environment = (String)Configure::read('Environment.name');
        if (empty($environment) && !empty($_SERVER['HTTP_HOST'])) {
            $server = (String)$_SERVER['HTTP_HOST'];
            if (!empty($server)) {
                foreach ($this->environments as $e) {
                    if (isset($this->{$e}) && isset($this->{$e}['environment']) && $this->{$e}['environment'] == $server) {
                        $environment = $e;
                        break;
                    }
                }
            }
        }
        return $environment;
    }
}

Example for your database.php:

App::import('Lib', 'BaseConfig');

class DATABASE_CONFIG extends BASE_CONFIG {
    var $default = array(	// localhost
        'name' => 'default',
        'environment' => 'localhost',
        'driver' => 'mysqli',
        'persistent' => false,
        'host' => 'localhost',
        'login' => 'root',
        'password' => '',
        'database' => 'cake_app',
        'prefix' => 'xyz_',
        'encoding' => 'utf8'
    );
    var $sandbox = array(	// online test
        'name' => 'test',
        'environment' => 'test.domain.com',
        'login' => 'root',
        'password' => '',
        'database' => 'cake_app_test',
    );
    var $online = array(	// online productive
        'name' => 'online',
        'environment' => 'www.domain.com',
        'login' => 'root',
        'password' => '',
        'database' => 'cake_app',
    );
}

The same config file on all 3 locations will now select the one corresponding to the environment url.
You could override this, though, by using Configure::write(‘Environment.name’) – but this is not necessary if the domain doesnt change too often.

Debug Mode

Put this in your core.php (it should ALWAYS be 0 by default!):

Configure::write('debug', 0);
# Enhancement
if (!empty($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'] == 'localhost') {
    Configure::write('debug', 2);
}

Debug mode is 0 for all online sites and 2 for your local development site.

Custom "Overrides"

There are many variables you need to switch from local to live apps (google.maps key, other api keys, email server credentials, …)
The quick-and-dirty solution would be something like this:

if ($_SERVER['HTTP_HOST'] == 'localhost') {
    $config['Foo'] = array(1,2,3);
} elseif ($_SERVER['HTTP_HOST'] == 'www.domain.com')) {
    $config['Foo'] = array(1,2,4);
} else {
    $config['Foo'] = array(1,5,6);
}

Very fast very ugly.

A little bit cleaner is using two config files: "configs.php" and "configs_private.php". The second one is not synched (or in SVN) – it contains passwords and environment specific content.
Include it in your bootstrap AFTER you included the default configs.

As i just mentioned, it has another upside: Beeing a environment based tmp-file it does not store any sensitive information in the SVN (or whatever other backup tool you use).
This way you can easily set up configuration "stubs" in your configs.php and insert the passwords in your private config file.

Folder Setup and .htaccess

You can use the same folder setup:

  • cake
  • app
  • vendors
    With …/app/webroot/ as "visible folder" and …/app/webroot/index.php as dispatching script.

In my htaccess file i use "!^localhost" to avoid redirects locally:

RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} !^localhost [NC]
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]

This way the same file can be used for both environments.

Linux/Windows differences

Use the constant DS (= Directory Separator) anywhere you can. If you hardcode it with / (Linux) oder \ (Windows) it might break some code. Usually it doesn’t, but it is cleaner to use DS anyway.

Example:

file_put_contents(TMP.'xyz'.DS.'file.txt', 'some text')

Set up a WINDOWS constant in your bootstrap.php – this way you can dynamically decide what function to use or to not use specific lines of code (like console "exec" commands).

if (substr(PHP_OS, 0, 3) == 'WIN') {
    define('WINDOWS', true);
} else {
    define('WINDOWS', false);
}

Example:

if (WINDOWS) {
        //whatever
} else {
        exec($something);
}

Uploading your changes

I use a shell tool i wrote – which uses "rsynch" and only uploads the delta (changes made so far).
Make sure you DON’T upload any tmp stuff or even worse, override environment based files like uploads, cached files etc.
Using batch scripts you can usually exclude some directories.

Final Tips

Remember that linux needs explicit folder permissions. So you need to manually (or per batch script) set /tmp to 777 recursivly as well as any other folder which you want to write into from your scripts.

Also clear the cache after updating! Otherwise your model relations as well as some constants will be outdated and cause errors or complete failure.

6 Comments

  1. hey mark

    good article, useful information. but will this work with CakePHP 1.2 too? afaik there’s no lib folder there, so where should I put this stuff?

    thanks

  2. Sure
    I used this in 1.2 – although slightly modified.
    You can import a component (one that doesn’t extend Object!).

  3. Thanks again for this great article, Mark. I’m running web tests now from tests.myapp.localhost instead of myapp.localhost, and this way CakePHP connects to the test-db instead of the normal db. Very useful (can’t think of a reason why this isn’t done by CakePHP anyway).

  4. Another question: why do you use

    if ($environment && isset($this->{$environment})) {
                  $this->default = array_merge($this->default, $this->{$environment});
              }
              $this->test = $this->default;

    instead of simply

    if ($environment && isset($this->{$environment})) {
                  $this->test = array_merge($this->default, $this->{$environment});
              }
  5. I guess you miss the point.
    I am primarily setting the default database setting. for the MAIN app. That’s what this is for.
    The script should chose the appropriate setting according to the current environment (on my windows machine, on the linux test server, on the linux live server, …)

    The tests only happens to have the same setting (for me anyway – you can handle that separately, of course). This way it is easier to maintain. I use the same database, but with a different prefix (so it does not interfere with live data)..

  6. Hi,
    this article is superb reading 🙂 thx 4 taking the time to write this Tutorial I’m going to try this on my 2.x production setup… I hope It wount break the cake bake Shell 🙂

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.