Moving your PHP code into reusable composer packages
Writing code for anything takes time. Time usually spent lifting some components from other projects. After a while, you've probably already got 99% of the initial setup code for the new project elsewhere, floating around in your git repositories. Imagine just reusing this code from a single place in multiple projects. This is exactly the job of a dependency manager.
Every developer has been there. You load your editor for your new project, load the editor for an old project, and painstakingly start copying and pasting code.
Let's say you've got a class within your code, and it's complicated. You don't want to rewrite it again. Likewise, you know if there's a bug you'll likely need to fix it across several projects. This is the perfect use case for packaging it up for Composer.
What is Composer?
Composer is a dependency manager system for PHP. It's a way of forcing a project to use a very specific set of libraries at very specific points in their development history. For example, you could force a library called SomeLib
to use exactly version 1.2.345
. It's very much similar to NPM for Node.js and Gradle for Java.
Right, but how do we get started with it?
First things first, Composer can either use public repositories (you'll find these on Packagist), or alternatively it can also use private repositories. We'll focus on private repositories for this exercise. There's a full working example of this in our GitHub organisation that you can refer to if you get stuck.
Let's imagine you have this class below and it's very complicated logic (I know, it's pretty basic looking, but bear with me!). Now, you've written it, and it's beautiful. So beautiful in fact that you're proud of yourself for writing something so great. Take a picture, send it around to your friends, boast on Twitter. Celebrate it!
Initially we want an entirely empty directory. It doesn't really matter what you call it, just make a directory, somewhere.
Within that directory we're going to need a folder called src
and a file called composer.json
The directory should look like this now:
Let's copy the class from above into the src/
directory. The structure will now look like this:
src/
src/MySuperClass.php
composer.json
Next we need to edit that composer.json
file. We'll start with the basic working version and get into the more complicated controls afterwards. Add this content to your empty composer.json
file, changing the value of the "name"
section to be your-git-username/your-package-name
. You might end up with something like...
For our example, we're using the package name of district-5/blog-example-composer-package
because our GitHub username is district-5
and the repository is called blog-example-composer-package
(see this repository). This just makes things easier to translate, although you can name them anything you want.
We can give this a quick test. Assuming you have Composer set up (if not, visit here and install Composer before returning to the instructions).
In your terminal (or shell), run:
Composer will output the following. If there's an error, or something went wrong, look back at the examples above and make sure you've not made a mistake somewhere.
If it looks the same as the above, we can move on. You'll likely notice that there are some new items in the project tree. Specifically, a new composer.lock
file which contains all of the data needed for composer to reinstall the package, and a folder called vendor
. The vendor
folder will contain the all-important autoload.php
. The autoload.php
file contains the class mappings to automatically load a class when you ask for it.
Let's give it a quick test...
In the root directory (the same directory as your composer.json
file we'll create a test.php
file. Don't worry about this too much. We'll be removing it again in a second. Within this file you'll need the following content...
In your terminal, within the directory containing the test.php
file, we'll run a quick command to check the output.
Hopefully the output from this is string(5) "world"
. If it is, great! If it's not, it's likely you've changed either the namespace in the PHP file, or the namespace in the "autoload"
section of the composer.json
file.
We're finished with the test.php
file, so you can delete it now.
At this point, you've got everything you need to start packaging, so let's move on to the finer tuning. If you'd rather stay at this point, you can skip to the "Let's get packaging" section below.
The example class we used here has a typed property, which means it will 'require' at least PHP version 7.4 to work. We can add this to a new "require"
section within the Composer file. Add this section to the middle of the composer file (I generally place it above the "autoload"
section).
What we've actually done now is prevent any projects using less than PHP 7.4 from using this library. Nice 😎
Let's get packaging!
We're using git for this example, so to prevent the repository being filled with files we don't need, we'll create a .gitignore
file.
In this file, we'll be excluding files from git that we just don't need. For example, on a Mac you'll want to ignore .DS_Store
specifically, but for Windows there are files such as the Thumbs.db
file that you'll want to also exclude. GitHub runs an excellent repository of .gitignore
files here, and we've included out .gitignore file in our repository for this example.
For the basic example here let's just add the basics. Add the next lines into your .gitignore
file...
At this point, the only paths that will get committed to git are:
Let's initialise the repository and push it to your origin repository...
Let's check which files are pending...
Add these files to staging...
Commit the files...
Assuming you have a git repository set up already, let's add the origin and push. Remember to change the git repository link below
Now we need to push that commit into the new repository. We can do this by issuing...
Amazing. But let's create a tag of this code also. By doing so we lock in the version numbers. We'll start with 0.0.1
. After we've created the tag, we'll also push it.
Using the tagged version...
We'll leave the library now. Let's go into a project directory and create (or edit) a composer.json
file.
Because in this example we're using a a private repository, we'll need to tell the project level composer.json
(not the library file from above) where to find the the code. But if you're using a public repository, and you've set things up with Packagist, you can just omit the "repositories"
section entirely.
What we've done now is tell the project Composer where to find our district-5/blog-example-composer-package
and to always use version 0.0.1
. You can alter the value from "0.0.1"
to be >=0.0.1
if the required version should be at least 0.0.1
.
At this point, as long as your project references the vendor/autoload.php
file before usage of the library class you'll be reusing code!
Updating the library...
Let's say you've just made a change to the library and you want to update it in the project. Go ahead and change your library code, commit and push the change to master. Next you'll want to create the tag using the git tag -a 0.0.2 -m 0.0.2
and push it with git push origin 0.0.2
.
Load the project (not the library). If you used 0.0.1
as the version in the project you'll edit the project level composer.json
file now to contain the following, but if you've used >=0.0.1
you can skip this next snippet...
Next we'll update just this library. Be extra careful with this one. If you ran a global update you'd likely break another dependency.
Within the project directory (not the library)...
There you have it. Your project is now using version 0.0.2
of the library.
Good to know...
Projects are slightly different to libraries. Typically speaking, a library should not contain a composer.lock
file in the git history, and neither a project or library should ever have the vendor
directory committed. The versions required should be dictated by your project, although there are use cases where you may want to store the lock file for the library also.
Here at District5 we don't believe in writing the same code twice. By keeping clean libraries of code, we're able to vastly decrease development costs for our customers by reusing instead of rewriting.
We're incredibly proud to be proactive about squashing bugs and rolling out bug fixes for our customers in a timely fashion. Packaging plays a huge part in our business and helps streamline development.