I have worked on this Scholarships project for about two and a half years.  It’s going to be replaced by another application, so I have reviewed how we did this and what I’ve learned in the process.  Modernizing a CodeIgniter Application – The Problems discusses the problems we were trying to solve (TLDR; we had a legacy CodeIgniter application with a giant App model full of functions making DB calls).  This post shares how we bootstrapped the CodeIgniter application to allow us to bring new code online without breaking the pre-existing application.  The reason we had to do this is also described in the previous post.

It’s important to me that I mention my coworker, Dustin Wheeler, figured out how to do most of this, and I have also learned a lot from Mitch Amiano during this project.  I am trying to document the ways that we modernized this Application to make sure that I am able to do it again.  Most of my development life has involved taking on existing applications, so this seems like a useful bit of information.

Screen Shot of Early Scholarships Working Tree

Screen Shot of Early Scholarships Working Tree

This is what the tree basically looked like at the beginning:

The directories /_data/app, /dinner, and /public already existed.  The bulk of the Application existed in the app directory.  Dinner was another little application on the side.

The first steps included creating a composer.json file that named the application, set the default namespace and mapped that to a new directory called /src.

The initial composer.json configuration file.

Initial composer.json file

 

 

 

Next we installed Phinx for database migrations and PHPUnit and Codeception for tests.  After the bootstrapping process, we used Codeception acceptance tests to verify that the data displayed on the Student Application stayed the same when we built entirely new views and changed the workflow around how the students applied for Scholarships.

Configuration

In index.php, we turned on error_reporting and required the composer autoloader.

If no file_exists vendor/autoload.php, throw an exception. Otherwise require vendor/autoload.php

Adding composer autoloader to index.php

Next we had a few fixes to CodeIgniter bugs.  We removed a call to set_magic_quotes_runtime() in the file /_data/system/codeigniter/CodeIgniter.php.  In another file /_data/system/codeigniter/Common.php we removed the & from the following statement: $objects[$class] =& new $name();

We built two .sql files for the two databases used by our application.  In these files we saved the create schemas for each of the databases from Production. These files were later populated with anonymized test data, since we couldn’t use anything personally identifiable about our students or faculty.  We kept most of the scholarship data the same, so we could test against real scholarship matching criteria, but we anonymized the contact info in those as well.

We created a debug controller that later went away.  I’ll come back to note its purpose.  I believe it was to test new code from /src being pulled in.

The database configuration had persistent connections turned on, and we set them false:

$db['default']['pconnect'] = false;

$db['default']['db_debug'] = false;

$db['default']['cache_on'] = false;

A base database configuration script became pretty useful for three developers.  It began as a deploy script, but later evolved into a fully incorporated configuration file.  In the same file as above, the /app/config/database.php, we anonymized the hostname, username, password, and database to things like DB_HOST,  DB_USERNAME, DB_PASSWORD,  and DB_NAME.  The deploy script let us set these to something real in our development environment.  I have pasted the bulk of that script, but this script will only configure one database.  It seemed more readable and shorter this way.

#!/bin/bash

########################################################
# Start Application Configuration
########################################################
if [[-f app/config/config.php ]]
then
        echo "Existing Application Config:"
        GREP "APP_" app.config.config.php
fi

APP_BASE_URL="http://localhost:8080/"
APP_INDEX_PAGE="index.php"

echo "Configure Application Config (1/1):"
read -p "Application Base URL ($APP_BASE_URL): " base_url
read -p "Application Index Page ($APP_INDEX_PAGE): " app_index_page

APP_BASE_URL=${base_url:-$APP_BASE_URL}
APP_INDEX_PAGE=${app_index_page:-$APP_INDEX_PAGE}

sed "
    s/APP_BASE_URL/$APP_BASE_URL/g;
    s/APP_INDEX_PAGE/$APP_INDEX_PAGE/g;
" app/config/config.default.php > app/config/config.php

echo "Rewrote app/config/config.php"

########################################################
# Start Database Configuration
########################################################

if [[ -f app/config/database.php ]]
then
    echo "Existing Config:"
    grep "$db" app/config/database.php
fi

# Default Configuration Values
DB_HOST="localhost"
DB_NAME="scholarship"
DB_USERNAME="root"
DB_PASSWORD=""

echo "Configuring Scholarship Database (1/2):"
read -p "DB Username ($DB_USERNAME):" username
read -p "DB Password ($DB_PASSWORD):" password
read -p "DB HOST ($DB_HOST):" dbhost
read -p "DB Name ($DB_NAME):" dbname

DB_USERNAME=${username:-$DB_USERNAME}
DB_PASSWORD=${password:-$DB_PASSWORD}
DB_HOST=${dbhost:-$DB_HOST}
DB_NAME=${dbname:-$DB_NAME}


sed "
    s/DB_USERNAME/$DB_USERNAME/g;
    s/DB_PASSWORD/$DB_PASSWORD/g;
    s/DB_HOST/$DB_HOST/g;
    s/DB_NAME/$DB_NAME/g;

" app/config/database.default.php > app/config/database.php

echo "Rewrote app/config/database.php"
sed -i "
    s/DB_USERNAME/$DB_USERNAME/g;
    s/DB_PASSWORD/$DB_PASSWORD/g;
    s/DB_HOST/$DB_HOST/g;
    s/DB_NAME/$DB_NAME/g;
" app/config/config.php

echo "Updated app/config/config.php because stupid junk."

########################################################
# Import base schemas for databases.
# Should kick of migrations in future.
########################################################

# Import schema for default database.
mysql -u ${DB_USERNAME} -p${DB_PASSWORD} -h ${DB_HOST} < database/seeds/default.sql

Modifying CodeIgniter to Allow Testing

In the file /_data/system/libraries/Loader.php, we removed the line from function CI_Loader():

global $OUT;

with the following line:

$OUT =& load_class('Output');

In the file /app/config/config.php we set $config[‘enable_hooks’] to TRUE.

In the file /app/config/hooks.php we added this hook:

$hook['display_override'] = array(
    'class' => 'DisplayHook',
    'function' => 'captureOutput',
    'filename' => 'DisplayHook.php',
    'filepath' => 'hooks'
);

I’m pretty sure that hook is for Codeception to capture output using PhantomJS.  At this point, we began with a new Codeception test suite for the Student Application.  This part of our Application was mostly changing visually.  When a student completed their application and submitted the information, we created a StudentSubmittedApplication event instead of saving an entire set of data in a database row, as the previous application was working.  But this is getting a little ahead of the game.  We’re bootstrapping here.

In the file /app/hooks/DisplayHook.php, we told it to get the APP_ENV variable

<?php
// application/hooks/DisplayHook.php
class DisplayHook {
    public function captureOutput() {
        $this->CI =& get_instance();

        $output = $this->CI->output->get_output();

        if (getenv('APP_ENV') != 'testing') {
            echo $output;

            if(getenv('APP_ENV') != 'production') {
                if ($this->requestIsNotAjax()) {
                    echo sprintf(
                        '<img id="debug-overlay" src="%spublic/images/debug-overlay.png" /><style>#debug-overlay { position:fixed; top:0; left:0; z-index:-1; width:900px; opacity:0.25; }</style>',
                        base_url()
                    );
                }
            }
        }
    }

    private function requestIsNotAjax()
    {
        return  !(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest");
    }
}

We set this value in a .htaccess file for Apache:

SetEnv APP_ENV development

At this point, we were writing a lot of tests because the existing application had none, and the student application was being redesigned.  There is a lot more to describe here, but I’ve got to parse through a lot of UI changes to find the useful bits.  I’m going post this as a Part One.  Part Two will include Pimple containers and bindings, controller actions to reach new code in /src, and other fun stuff.  Please feel free to post questions.  I would like to keep this as useful as possible.