Using Composer VCS to Refactor with Volunteer Teams

Tag: Composer

It's a common theme. There's this site - maybe a game, maybe something for an organization or church you belong to - and they need volunteers. And you'd like to help, you really would, only...it's 10 year old procedural code, with no migrations or data seeders, running on php 5.2, without a single test... and honestly, between giving up your entire Saturday to try to get this rickety mess running on your local, and then actually committing enough time to working on it to make the whole thing worthwhile...well, maybe not this month.

From the perspective of the site's current team, there are issues as well. They probably don't want every Tom, Dick and Sally having full access to their business logic. It may not be particularly easy to get a sanitized version of the database to distribute. Plus, there's the old "disappearing half-way through the code rewrite" volunteer attrition problem.

Hopeless, right? Absolutely not! Not only is it perfectly plausible to deal with, but the solution itself will push the entire project into best practices - refactoring it piecemeal into decoupled, tested code libraries.

Enter Composer VCS!

By now you've surely heard of Composer and are most likely using it extensively in your projects. Most people use it via it's directory site, Packagist, but less familiar is the fact that it can be used to auto-load fromany repository - even on your local system! For this project, though, we will set things up with a public Bitbucket repository.

Here is the idea we are after - instead of approaching this as a single, large refactoring project we might normally hire a consultant to come in and do for several months, we will snip out small parts and put them into classes. Only, the classes will be in separate repositories and added to our site with Composer.

Why? Why so many extra steps? Because what we are doing is allowing anyone to come in, create a small custom library for us with tests and good design patterns - and leave! They don't need to know about the rest of the site. They don't need access to our database. They won't - they CAN'T - couple their code to ours, because they don't know about ours. But we still own it - we own the repository they are pushing to. This makes it much easier for people to contribute by making small commitments that they can often do in a few hours.

That's the idea - let's walk through a really simple example.

// attackMonster.php

/* Quite commonly, you will see this sort of db lookup in legacy code: */

// include 'db_functions.php';

// $attackerId = $_GET['id'];
// $attacker = db_query('SELECT * FROM characters WHERE id =' . $attackerId);

// $monsterID = $_GET['monsterId'];
// $monster = db_query('SELECT * FROM monsters WHERE id =' . $monsterID);

/* Instead of above, I will just make some arrays so this code actually functions: */

$attacker = ['id' => 123, 'strength' => 20];
$monster = ['id' => 74, 'strength' => 63];

$attackerStrength = $attacker['strength'] + rand(0, 30);

if ($attackerStrength > $monster['strength']) {
    echo 'You win!';
} else {
    echo 'You died!';
}

// more stuff...

Players are complaining that their character always dies, but the developer says everything looks fine and it's just bad luck. (In this case, he does always die, but remember these would normally be dynamic values from the db). after some discussion, it is agreed to refactor this with tests to prove everything is fine.

An outside volunteer agrees to lend a hand with this task. The site's developer team (not the new volunteer) creates a private repository such as this and sets up permissions for the programmer. (I've made this one public so you can actually build this tutorial on your own. Everything below actually works - go ahead and try it).

With any sort of tutorial such as this, it is always hard to know where to draw the line on related topics. I want this to be a focus on using Composer VCS and so I'm not going to actually refactor the code, only move it to the outside library, and I'll show the phpunit import but not actually write tests. DO THESE THINGS! It is the whole point, after all.

Here's where you think "inside out" a little bit. The programmer will tell you - "I need you to pass me two arrays (the attacker and the monster) with ids and strengths, and I will use the logic you gave me to return a string to you with the result of the battle". In a real refactoring project, he would probably create an interface for those arrays and you would need to pass him instances.

THIS IS IMPORTANT TO UNDERSTAND! The new AttackMonster library doesn't know anything about your site, so he must define exactly what input you send him. This is why when you use a project like Guzzle, you often have to create a new `Stream`, and pass the Stream into the GuzzleClient. That's how this all works - AttackMonster doesn't know anything about you, and could even be a component in dozens of unrelated games.

Now he is finished and you are ready to pull in his library and refactor. This will be almost exactly like using any other composer library you know from packagist, with just some slight differences in the composer.json file:

// YOUR com@poser.json file
{
    "name": "codebyjeff/megamonsterattack",
    "require": {
        "codebyjeff/attackMonster": "dev-master"
    },
    "repositories": [
        {
            "url": "git@bitbucket.org:jrmadsen67/monsterattack.git",
            "type": "vcs"
        }
    ]
}

Two lines to notice here:

// from HIS composer.json, where he puts the name (from here:
https://bitbucket.org/jrmadsen67/monsterattack/src/3bee04ced18bf16204c1a8c3d6fa525628f8917d/composer.json?fileviewer=file-view-default)
"codebyjeff/attackMonster": "dev-master"

// from the project Bitbucket page, where it shows you how to clone (from here:
https://bitbucket.org/jrmadsen67/monsterattack)
"url": "git@bitbucket.org:jrmadsen67/monsterattack.git"

Now we can refactor our original file to use the nicely rebuilt attackMonster class:

// if you are using a framework, or a single `header.php` file of some sorts,
// you don't need this everywhere. This loads your autoloader so you don't
// need to `require` all the class files
require 'vendor\autoload.php';

// we `use` the class with its namespace
use MyGameName\AttackMonster\AttackMonster;

// same data from our source - our new library doesn't know where this comes from
$attacker = ['id' => 123, 'strength' => 20];
$monster = ['id' => 74, 'strength' => 63];

// our lovely tested class the volunteer built for us
$AttackMonster = new AttackMonster;
echo $AttackMonster->attack($attacker, $monster);

Each time some bit of code needs a bug fix or additional features, rinse and repeat. Eventually what you'll have left is a lot of tested classes with some "glue" that could be moved over to a framework, or just used as is with much more ease and confidence.

Hope that helps!

Read it Monday, Use it by Friday!

Laravel Quick Tips Weekly Newsletter. Short, immediately helpful bits you'll use in your own codebase before the next one arrives.

Join us now and get a FREE PDF of the first fix months of Laravel Quick Tips!

No Spam, Unsubscribe Anytime

Contact me