How We Migrated a Legacy Code Library into a Modern Composer Package

by | Nov 24, 2015

Introduction:

A couple projects ago, a client of ours wanted a simple forum system. Nothing nearly as complicated and feature-filled as phpBB or other high grade options. The decision was made to go with Bbii. Sadly Bbii maintainer has yet to convert the module into a composer ready package, so the module was added to the project manually. Fast-forward a couple months and the initial Bbii2 from Sourcetoad has gained some traction and we can now circle back to it and ‘Packagist-ize’ it. Hence this walk through. Why would one want to have a code library as a package? The answer is a short and simple one: management. Ease of update, publish, and distribution.

 

NOT covered in this blog series:

  • CI/CD
  • testing
  • cross framework integrations
  • backwards compatibility
  • code standards

 

Assumptions

  • Comfortable in the terminal
  • Can setup a modern framework project (specifically Yii2 or similar) – http://www.yiiframework.com/
  • Understands dependency management concepts – https://getcomposer.org/doc/00-intro.md
  • Handy with the Find/Replace tool
  • Class and namespace autoloading – http://php.net/manual/en/language.namespaces.php
  • Basic understanding of Semver – http://semver.org/
  • GiT (though I believe this will work with svn with some tweaking, ymmv)

 

Parent application

First thing we had to do was setup and project to encompass the entire process. Since the library was initially built for a Yii2 project we will start there.

`cd {project_root}`
`git clone https://github.com/trntv/yii2-starter-kit ./`
`cp .env.dist .env && nano .env` #Edit the .env values as needed for your environment.
`composer global require "fxp/composer-asset-plugin" -o -v`
`composer install -o -v`
`php console/yii app/setup --interactive=0`

At this point we should have a basic Yii2 web-app up-and-running:

Yii2...installed.

 

Package skeleton

Now that the parent application is setup we will move on to dealing with some of the paperwork of creating the package.

`cd /{project_root}/vendor/`

You should not see a list of ‘vendors’. Each vendor can, and many time does, have multiple ‘packages’ within them. Some simple, some massive, some you may never use (but other packages do). Some you may use heavily. We’ll now make our own vendor directory and step into it:

`mkdir sourcetoad && cd sourcetoad`

The internal structure of each package varies widely as the functionality and usage of packages vary. There is no authoritative source on how to construct the internals of a package. However our top two levels for this specific usage is similar to this other package:

Screen Shot 2015-11-24 at 11.18.21

Screen Shot 2015-11-24 at 11.18.34

`mkdir bbii2 && mkdir ./bbii2/src && mkdir ./bbii2/tests`

Now initialize a repo to track all our changes like the good developers we are:

`touch README.md && touch LICENSE.md && git init && git commit -m "INIT commit`""

We now manually copy/paste all of our library’s code as is, into the `/{project_root}/vendor/sourcetoad/bbii2/src/` directory and commit this initial code addition.

`git add -A && git commit -m "Init code add of library logic into package"`

The magical `composer.json`

Composer looks for a file called `composer.json` in the root of every package or application it deals with. This is a staunch and non-negotiable part of Composer. We added the one for this library.

`touch composer.json`

The details about the `composer.json` abilities will not be handled here. Instead head over to https://getcomposer.org/doc/ and check it out; it is very in-depth. For this tutorial, though, here’s what we need to add to `/{project root}/vendor/sourcetoad/bbii2/composer.json`

{
     "name": "sourcetoad/bbii2",
     "description": "Bbii2 is an iteration of the bbii forum module, but migrated to Yii2",
     "keywords": ["forum", "module", "sourcetoad", "yii2"],
     "type": "module",
     "authors": [
         {
             "name": "Sourcetoad",
             "website": "https://www.sourcetoad.com/"
         },
         {
             "name": "David J Eddy",
             "email": "me@davidjdeddy.com"
         }
     ],
     "require": {
         "yiisoft/yii2": "^2"
     },
     "autoload": {
         "psr-4": {
             "sourcetoad\bbii2\": "/src"
         }
     }
}

A note about the PSR-4 autoload: it can get a bit tricky, for the sake of clarity here is how we do it at the moment:

"psr-4": {
    "{vendor name}\{package name}\" : "/{root child directory}"
}

The autoloader is VERY important as it will determine the namespacing used in the “use” statements of our current logic, as well as how the package will get found and auto-loaded by composer on install or update. Rule of thumb: keep it simple.

Now let’s commit the changes:

`git add -A && git commit -m "init codebase and documentation add"`

Now we do a global find/replace and changing all the namespace “use” statements throughout the library. This is well outside the scope of this tutorial but as an example:
Currently in “/{project_root}/vendor/sourcetoad/bbii2/src/Module.php”:

 namespace frontendmodulesbbii;

 use frontendmodulesbbiimodelsBbiiMember;
 use frontendmodulesbbiimodelsBbiiSpider;
 use frontendmodulesbbiimodelsBbiiSession;

 use Yii;
 ...
 class Module extends yiibaseModule
 {
 ...
 }

… will need to be changed to …

 namespace frontendmodulesbbii;

 use sourcetoadbbii2modelsBbiiMember;
 use sourcetoadbbii2modelsBbiiSpider;
 use sourcetoadbbii2modelsBbiiSession;

 use Yii;
 ...
 class Module extends yiibaseModule
 {
 ...
 }

This will have to be done globally throughout all the library’s classes. Find/Replace is your friend.

Screen Shot 2015-11-24 at 12.00.02

Screen Shot 2015-11-24 at 12.04.56

And now we commit our changes to the repo:

`git add -A && git commit -m "GLOBALLY REPLACED namespaces"`

For the sake of safety we then pushed all the current code to the remote origin

`git push origin`

Testing the package

Now we want to test the package within our current test application, following the install process found in the README.md and add the following to our applications “./frontend/config/web.php” and add a menu item to the top main menu:

Screen Shot 2015-11-24 at 12.47.47

Screen Shot 2015-11-24 at 12.48.01

 

Next we head over to https://packagist.org/ to add the package to the composer list. You will need read access to the repository to do this.

 

Screen Shot 2015-11-24 at 14.13.01

Once the package was successfully indexed we got the following:

Screen Shot 2015-11-24 at 14.15.39

Now in order to keep our package that is available via composer up-to-date with any changes pushed, we had to add a webhook in our repository that will trigger the Packagist to update upon post-update. In GitHub this is very easy to do under the repositories “settings”.

Screen Shot 2015-11-24 at 14.17.23

 

Now we add the package as a dependency in our parent project’s composer.json:

...
    "bower-asset/jquery-slimscroll": "^1.3",
    "bower-asset/flot": "^0.8",

    "sourcetoad/bbii2": "dev-master@dev"
 },
 "require-dev": {
    "yiisoft/yii2-debug": "^2.0.0",
 ...

Finally, we now need to update the parent project with the new dependency.

`composer update sourcetoad/bbii2 --prefer-source -o -vv`

If everything goes as planned the output from composer should be similar to this:

Screen Shot 2015-11-24 at 14.25.35

Specifically we are looking to ensure composer finds and installs our new package and repository. We can also check-in `/{project root}/vendor/composer/autoload_psr4.php` and search for the vendor name.

 ...
 'trntv\filekit\' => array($vendorDir . '/trntv/yii2-file-kit/src'),
 'trntv\aceeditor\' => array($vendorDir . '/trntv/yii2-aceeditor'),
 'sourcetoad\bbii2\' => array($vendorDir . '/sourcetoad/bbii2/src'),
 'probe\' => array($vendorDir . '/trntv/probe/src'),
 'mihaildev\elfinder\' => array($vendorDir . '/mihaildev/yii2-elfinder'),
 ...

We see our vendor name is now able to autoload using the PSR-4 recommendations. At this point the framework can make easy usage of the package.

Back to the App

 

Clicking on the forum link we get…a database error!?! That’s right. The Bbii2 module requires some database tables to be added.
Migrations (Yii2 style)

Thankfully we have all the SQL logic from the original Bbii forums module (thanks a ton to Ronald van Belzen for being awesome and including them).

`cd /{project root}`

In Yii2 creating a Db migration is super easy, see the migration file for a bare bones example.

`./frontend/yii migrate/create bbii2_install --migrationPath=./vendor/sourcetoad/bbii2/migrations`

Now add the step to the READMe and commit to repo.

`git add -A && git commit -m "UPDATED README.md with clearer installation instructions." && git push origin`

Finally, we’ve gotten to a point where the page rendered:

Screen Shot 2015-11-24 at 14.47.02

*Not composer CAN be set to run automated scripts for different events. Such processes are outside the scope of this blog.

SemVer, tagged releases

SemVer (http://semver.org/) is a convenstion on how to number version releases. “Major.Minor.Fixes” is what it boils down to. Since this version of Bbii2 is not compatible with the previous version we bump the major number up. Our initial ‘new’ release will be called Bbii2 2.0.0

Screen Shot 2015-11-24 at 14.56.33

The great part about using SemVer tags with composer is that Packagist picks up on them automatically:

Screen Shot 2015-11-24 at 14.53.37

This lets consumers of your packages regulate what version (or range) composer will install from / update to with ease.

Wrapping Up

I will not lie to you, this could be a lot simpler. Git, namespace, SemVer, packagist, composer; all are required to work properly together for this to even a chance of working right. But when it does, oh goodness is it nice, as well as very open and flexible. This walk-through series only touches on the very edges of what each technology’s capabilities are. With their powers combined, dependency management becomes a light-hearted breeze. To make a change to a library (now package?), add a release tag, push it to origin, and you’re done. Everything else is automated; the new updates and code is near instantly available to your consumers.

Recent Posts