Deploy CakePHP Apps on AppFog the Right Way

    Vito Tardia
    Share

    This article is shared from one of our sister sites, CloudSpring. If you find it helpful, be sure to give them a visit!

    This article covers two topics: deploying an application to the AppFog cloud provider, and best practices for a well-structured PHP application.

    First we’ll build a simple note-taking application using CakePHP and its automated bake command. Then, I’ll show you one way to structure the application separating its components into:

    • application code
    • application data and settings
    • library and vendor code

    The app will finally be registered and published to AppFog.

    Sign Up with AppFog

    AppFog is a Platform as a Service (PaaS) provider built on top of the open-source Cloud Foundry project. It provides support for multiple programming languages and lets us deploy applications to multiple infrastructure providers, such as Amazon AWS, HP Openstack, Microsoft Azure, and Rackspace DFW.

    Signing up with AppFog is very simple; all you need is an email address and a password and you have access to their free plan which offers 2GB of RAM, 10 services (ex. databases) with a max of 100MB each, and a 50GB data transfer limit. That’s lot for a free service! Also, as they say, you can move your app to any Cloud Foundry compatible service.

    Go ahead and create your free AppFog account, and then we’ll continue.

    Create the Local Application

    Download the latest version of CakePHP and extract it on your development machine, and then rename it as your application (I choose FogNotes). Make this directory visible by your web server with a friendly URL (ex. http://fognotes.local), the document root should be the app/webroot directory, and create an empty MySQL database (ex. fognotes_local).

    In your browser, you should see the default homepage provided by CakePHP. Don’t worry about the warnings – we’ll take care of them soon enough.

    Now create a directory named data and two subdirectories within it named config and logs. Copy (or move) the app/tmp directory inside data and make this and the logs directory writable to the web server. As a finishing touch, create an alias of CakePHP’s console command cake in the root directory of your application.

    ~/FogNotes$ ln -s lib/Cake/Console/cake cake

    Your directory structure should look like this:

    FogNotes/
        app/
        data/
            config/
            logs/
            tmp/
                cache/
                logs/
                sessions/
                tests/
        lib/
            Cake/
        plugins/
        vendors/
        cake
        index.php

    By doing this, we have separated the specific application code (app) from the framework (lib/Cake) and libraries (plugins and vendors). Also, the application’s data and settings are all in one separate place (data/*). Now we can we can now update, backup, and maintain each of the components separately. This is much desired in a real-world application!

    With the directory structure in place, let’s tell CakePHP to use it. We must set the custom temp directory, so edit app/webroot/index.php and paste this code just after the define('ROOT',...) statement:

    <?php
    // Custom TMP directory
    if (!defined('TMP')) {
        define('TMP', ROOT . DS . 'data' . DS . 'tmp' . DS);
    }

    Now for the database. Normally the database connection settings are stored inside the app/Config directory in a file named database.php. We’ll modify this setting in order to have the framework look in data/config directory for a file whose named after our current environment instead (ex. data/config/local.php).

    Copy the file app/Config/database.php.default to app/Config/database.php and replace the DATABASE_CONFIG class with this code:

    <?php
    class DATABASE_CONFIG
    {
        public $default = null;
        public $test    = null;
        public $env     = null;
    
        /**
         * Switch configuration based on environment
         */
        function __construct() {
            // Check that a suitable environment is defined
            if (!defined('APP_ENV')) {
                return false;
            }
    
            $this->env = APP_ENV;
    
            // Try to read database settings from the current ENV file
            $config = Configure::read('Database.config');
            if (!is_array($config)) {
                return false;
            }
    
            // Load all config database profiles
            foreach ($config as $name => $data) {
                $this->$name = $data;
            }
    
            // Throw an error if there is no suitable configuration
            if (empty($config['default']) || empty($this->default)) {
                return false;
            }
        }
    }

    This trick was adapted from an article published in CakePHP developers community by eimermusic. The environment will be defined using the bootstrap file or reading an environment variable provided by AppFog and the associated config file will be loaded.

    The app/Config/bootstrap.php file is the startup file for the application. Near the bottom of the file, after the Configure::write('Dispatcher.filters'...) statement, insert this code that registers the data/config path with CakePHP:

    <?php
    // Define a custom reader to search for config files
    App::uses('PhpReader', 'Configure');
    Configure::config('default',
        new PhpReader(ROOT . DS . 'data' . DS . 'config' . DS));
    // Determine the environment
    if ($env = getenv('APP_ENV')) {
        if (!defined('APP_ENV')) define('APP_ENV', $env);
    }
    else {
        if (!defined('APP_ENV')) define('APP_ENV', 'local');
    }

    AppFog will let us set the APP_ENV variable. If it’s not set, then the app considers itself in local mode.

    Now skip to the end of the file and paste this code:

    <?php
    try {
        Configure::load(APP_ENV);
    }
    catch (ConfigureException $e) {
        // do something, for example exit application
    }

    The Configure::load() statement tells CakePHP to search the data/config directory for a file named like the current environment (exs. local.php or prod.php). Within this file we can override all the settings defined earlier by the application.

    A sample local.php config file could be:

    <?php
    // Load base configuration. Any of the core settings can be
    // overridden here. Don't delete the $config var!
    $config = array(
        'debug' => 1
    );
    
    // If declared in the $config array, String::UUID() crashes
    Configure::write('Security.salt', 'SomeSuperSecretLongString');
    Configure::write('Security.cipherSeed', 'SomeSuperSecretLongNumber');
    
    // Database settings
    Configure::write('Database.config', array(
        'default' => array(
            'datasource' => 'Database/Mysql',
            'persistent' => false,
            'host' => 'localhost',
            'login' => 'root',
            'password' => 'secret_password',
            'database' => 'fognotes_local',
            'prefix' => '',
            'encoding' => 'utf8',
            'unix_socket' => '/tmp/mysql.sock',
        )
    ));
    
    // Custom Log file settings to ROOT/data/logs/
    CakeLog::config('default', array(
        'engine' => 'FileLog',
        'path'   => ROOT . DS . 'data' . DS . 'logs' . DS
    ));

    With all the customization in place, the warning messages in the default home page should disappear.

    Create the Application in AppFog

    Log into your AppFog console and create a new application. Be sure to choose PHP as the application type, your favorite infrastructure provider (I chose AWS Europe), and give it a name. Then click “Create App” and, in just a couple of seconds, the system will send you to the app’s “Mission Control”.

    The application with AppFog initally consists of a single index.php file with “Hello World” code; we have to upload our local code. But before we do, we need to bind a database service. Select the “Services” panel, click on the MySQL button, and give it a name (ex. fognotes_prod). The credentials to connect to the service are stored into the environment variable VCAP_SERVICES.

    We also need to go to the “Env Variables” panel and set our custom variable. Create a new variable named APP_ENV with the value of “prod” and save.

    Make a copy of data/config/local.php and name it data/config/prod.php and open it with your editor. We’ll extract the database settings following the guidelines provided by the AppFog docs. Edit the configuration file so that it looks like this:

    <?php
    // ...other code
    
    // AppFog Extraction
    $services_json = json_decode(getenv('VCAP_SERVICES'),true);
    $af_mysql_config = $services_json['mysql-5.1'][0]['credentials'];
    
    // Database settings
    Configure::write('Database.config', array(
        'default' => array(
            'datasource' => 'Database/Mysql',
            'persistent' => false,
            'host' => $af_mysql_config['hostname'],
            'login' => $af_mysql_config['username'],
            'password' => $af_mysql_config['password'],
            'database' => $af_mysql_config['name'],
            'prefix' => '',
            'encoding' => 'utf8',
        )
    ));
    
    // ...other code

    We could also bind the application to a custom domain or subdomain (for free!) but that’s not the case here.

    Now we’re almost ready to push our code! In the “Update Source Code” panel, find the instructions to install the af utility on your system. Open a terminal session and navigate into your application root. Follow the instructions to install the CLI tool, then login but don’t download the remote project code. Instead, go ahead with the command af update FogNotes to push the local code to the remote server. In less then a minute, your application is deployed and you can should see the same home screen as your local version. This means the application recognizes the production environment and the remote database.

    Adding Some Finishing Touches

    We have an empty application deployed, but we must add at least some code and data for our app. We’ll let CakePHP do the hard work.

    Create the file data/config/notes.sql with the following content:

    CREATE TABLE notes (
        id INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
        title VARCHAR( 255 ) NOT NULL ,
        content TEXT NOT NULL ,
        created DATETIME NULL DEFAULT NULL ,
        modified DATETIME NULL DEFAULT NULL ,
    
        INDEX (title, created, modified)
    )
    ENGINE = INNODB;

    Import the file into you local database on the command line or with your favorite MySQL tool.

    Next, from your app’s root directory, run the command:

    ~/FogNotes$ ./cake bake

    Follow the instructions (it’s really easy!) and create a model, view, and controller for “Notes”, which is the only table in the database. CakePHP will create the right files for you. Then, open the file app/Config/routes.php and replace the first Router::connect() statement with the following code to re-link the home page to the list of notes:

    <?php
    Router::connect('/', 
      array('controller' => 'notes', 'action' => 'index'));

    Reload the local home page and you should have a fully functioning application. Now it’s time to publish the changes. The command af update FogNotes will push the new code.

    You’ll see a nasty error page if you reload the remote app now. This is because we haven’t created the data table on the remote database. Woops!

    The easiest way to import tables is by creating a tunnel with the af tunnel command and then connect to the remote database using the info provided by the tunnel itself. Open the tunnel in a console window, select “none” as client type, and then you can use another terminal window to import the SQL file:

    mysql --protocol=TCP --host=localhost --port=10000 --user=RemoteUserName 
     --password=RemotePassword RemoteLongDifficultDBName < /path/to/notes.sql

    You can also use Sequel Pro if you are on a Mac or MySQL Workbench as long as the tunnel is open.

    Once the table is in place, the remote application will run smoothly again.

    Summary

    The organization of the sample application I showed to you here is just one of many techniques you can use to improve your CakePHP development processes. For example, you could store sessions to a file in your local machine and use MongoDB in an AppFog production instance.

    AppFog is a relatively young PaaS service, but is promising. It’s fast and easy to use, and it’s always evolving. AppFog also lets you clone an entire application, useful to create a staging or bug fixing environment. The rest is up to you, so happy coding!

    Image via Fotolia