Pulling Up Your Legacy App by Its Bootstraps
by Emily Stamey
by Emily Stamey
I'm a PHP developer
Organizer of Triangle PHP and Director of WWCode Raleigh/Durham
I work at a company that builds tools for network security threat assessment
Our Team for this project:
Our applications supported the College of Engineering, mostly outside of the classroom
We maintained at least 70 legacy applications
It's harder to read code than to write it. This is why code reuse is so hard.- Joel Spolsky
Everybody likes to write reusable code, and no one wants to reuse anybody else's code.- Kurt Koppelman (@moonhead)
The relationships between pieces of code are so tangled, it’s nearly impossible to add or change something without unpredictably breaking something.
Technique for restructuring an existing body of code, altering its internal structure without changing its external behavior.
Building onto an existing system for the purpose of improvement with the least amount of sweat equity and development cost in the process.
Don’t rely on developer feedback
Process was inconsistent with the application
Process exited and re-entered the system through spreadsheets
High Margin of Error
Many awards were rejected by Financial Aid
Didn’t trust that the best candidates were being chosen
Scoring algorithm was not clear/effective
Multiple majors weren't allowed
NOT ALL MONEY WAS BEING AWARDED
MONEY LEFT UN-GIVEN ⇒ ANGRY DONORS
Divided the application based on timeline
we didn't scope the work
{
"name": "itecs/scholarships",
"description": "Scholarships application for the College of Engineering.",
"require": {
"php": ">=5.3.3",
"robmorgan/phinx": "*",
"pimple/pimple": "~3.0",
"ncsu/auth": "dev-master"
},
"require-dev": {
"codeception/codeception": "2.0.*"
}
...
}
$ vendor/bin/phinx create CreateUserLoginsTable
<?php
use Phinx\Migration\AbstractMigration;
class CreateUserLoginsTable extends AbstractMigration
{
public function up()
{
$table = $this->table('users');
$table->renameColumn('bio', 'biography');
}
public function down()
{
$table = $this->table('users');
$table->renameColumn('biography', 'bio');
}
}
$ vendor/bin/phinx migrate
$ vendor/bin/phinx rollback
<?php
use Phinx\Migration\AbstractMigration;
class CreateUserLoginsTable extends AbstractMigration
{
public function change()
{
// create the table
$table = $this->table('user_logins');
$table->addColumn('user_id', 'integer')
->addColumn('created', 'datetime')
->create();
}
}
$ vendor/bin/phinx migrate
>
$ vendor/bin/phinx rollback
>
new code in '/src' alongside the '/app' directory
This allows us to reference a class like:
ITECS\Scholarships\Common\Services\GPAService
The dependencies are the objects your class needs to function
GPAService needed a DB connection
Instantiate the dependency as a parameter in the constructor or use a setter
function __construct(Database $database)
{
$this->database = $database;
}
<?php
namespace ITECS\Scholarships;
use \ReflectionClass;
use \Log;
/**
* Base Application Controller Class
*
*/
class BaseController extends \Controller {
const BAD_REQUEST=400;
const FORBIDDEN=403;
const NOT_FOUND=404;
const METHOD_NOT_ALLOWED=405;
const SERVER_ERROR=500;
protected $serverResponseCodes;
protected $authenticationService;
/**
* @var Pimple\Container
*/
protected $container = null;
/**
* Constructor
*/
public function BaseController()
{
parent::Controller();
// In Ameyrika, BaseController Contains Container
global $container;
$this->container = $container;
$this->authenticationService = $this->container['ITECS\Scholarships\IdentityAccess\AuthenticationService'];
session_start();
$this->setExceptionHandler();
log_message('debug', "Base Controller Class Initialized");
}
}
<?php
use Pimple\Container;
use Illuminate\Database\Capsule\Manager as Capsule;
$container = new Container();
$container['config'] = require_once('config.php');
$container['database'] = function ($c) {
$capsule = new Capsule;
// PHP 5.3 doesn't do array de-referencing.
// @todo update for php54
$config = $c['config'];
$capsule->addConnection(
array(
'driver' => 'mysql',
'host' => $config['db']['default']['hostname'],
'port' => $config['db']['default']['port'],
'database' => $config['db']['default']['database'],
'username' => $config['db']['default']['username'],
'password' => $config['db']['default']['password'],
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
),
'default'
);
// Make this Capsule instance available globally via static methods... (optional)
$capsule->setAsGlobal();
return $capsule;
};
'/app/config.php'
<?php
return array(
/* base url for path in the site */
'app' => array(
'base_url' => sprintf('http://localhost:%s/', isset($_SERVER['SERVER_PORT'])
? $_SERVER['SERVER_PORT'] : ''),
'index_page' => 'index.php/',
'debug' => FALSE
),
/* DB configuration */
'db' => array(
'default' => array(
'hostname' => "local__server",
'port' => "3306",
'username' => "uname",
'password' => "pword",
'database' => "db_name_"
)
),
'twig' => array(
'template_path' => array(
__DIR__ . '/templates',
__DIR__ . '/templates/dashboard',
)
),
'authorization' => array(
'funding' => array('elstamey', 'person2'),
'developers' => array('elstamey'),
'coordinators' => array('person3')
),
'notice' => array(
'enabled' => false,
'type' => 'info',
'headline' => null,
'message' => null,
'syslink' => null
),
'errorHandling' => array(
'emailExceptions' => false
),
'awardLetters' => array(
'letterhead' => '_itecs/notification-samples/ncsu_coe_aa_letterhead.pdf',
'academicYears' => '2015-2016',
'respondByDate' => 'May 11, 2015',
'templates' => array(
'awardLetter' => 'layouts/letters/award-letter.twig',
'awardEmail' => 'layouts/letters/award-email-body.twig'
),
'fixedAttachments' => array(
'_itecs/notification-samples/thank_you_template.pdf'
),
'signatureImagePath' => '_uploads/tmp/deans_signature.png',
'outputPath' => '_uploads/awardLetters/'
),
'emailService' => array(
'sendLimit' => 5,
'retries' => 3,
'throttleTime' => 2, // seconds
'fromAddress' => 'Scholarships Application <engr-webmaster@ncsu.edu>',
'replyToAddress'=>'College of Engineering <engineering@ncsu.edu>'
)
);
?>
We passed this array into a $container['config'] variable
'/app/bindings.php'
<?php
/* example two */
use Scholarships\Selection\ApplicantQueryService;
$container['Scholarships\Selection\ApplicantQueryService'] = function($c) {
return new ApplicantQueryService(
$c['Scholarships\StudentApplication\StudentQueryService'],
$c['Scholarships\Common\Services\ResidenciesService'],
$c['Scholarships\Common\Services\GpaService'],
$c['Scholarships\Common\Services\UnmetNeedService'],
$c['Scholarships\Common\Services\CandidateQualificationService']
);
};
<?php
class Selectionnext extends BaseController
{
public function Selectionnext()
{
parent::BaseController();
$this->scholarshipRepository = $this->container['Scholarships\Selection\Scholarship\ScholarshipRepository'];
$this->collaborationsService = $this->container['Scholarships\Selection\CollaborationsService'];
$this->committeeService = $this->container['Scholarships\Selection\CommitteeService'];
$this->authService = $this->container['Scholarships\IdentityAccess\AuthenticationService'];
$this->awardsService = $this->container['Scholarships\Selection\Scholarship\AwardsService'];
$this->events = $this->container['Scholarships\Support\Events\EventStore'];
}
private function getScholarshipDashboard($scholarshipId,$keepItLight=false)
{
$committeeMember = $this->getCommitteeMember();
$presenter = new ScholarshipDashboardPresenter(
$committeeMember,
ScholarshipId::fromString($scholarshipId),
$this->events,
$this->scholarshipRepository,
$this->container['Scholarships\Selection\ApplicantQueryService'],
$keepItLight
);
$scholDetails = $presenter->asArrayForJson();
$scholDetails['json_string'] = json_encode($scholDetails);
return $scholDetails;
}
?>
By May 2015: Approximately $1,074,394 Awarded
Tight deadlines with un-scoped work, we created technical debt that we would have to address in the next academic year
Recommended reading
Emily Stamey
Twitter: @elstamey
Joind.in: https://joind.in/talk/d4081