Music sheet

Composer Monorepo Without Plugins

By baohx2000 | Dev Thoughts | 4 Jun 2021


The problem: You have multiple services you want to build/ci/deploy. You want them all to have consistent dependencies between them all (one of the big reasons to utilize a monorepo).

It’s not difficult, you just have to think of your project as being a single segmented project. You don’t even have to utilize any extra funky composer plugins which might do weird things like making symlinks to bin commands.

Instead of having a composer.json file in each service, move that up to the level where all of your project directories live and generate a composer.json there.

Now, this part will take a little time… you will need to merge all of the requirements for all of your projects into the new composer.json.

Also in that composer.json, you will need to add autoload definitions for all of your projects… for example:

{
...
"autoload": {
"psr-4": {
"SomeCo\\Billing\\": "billing/src/",
"SomeCo\\Accounts\\": "accounts/src/",
"SomeCo\\Auth\\": "auth/src/",
"SomeCo\\DB\\": "libraries/DB/src/",
"SomeCo\\Config\\": "libraries/Config/src/"
}
}
...
}

Now, when you do your build step, you will run composer install from the new root project. This will build a vendor directory as a sibling to your project directories. To build the individual project, you will copy the vendor directory and the single project to your vm/docker image/whatever. If you have shared libraries, like the above example, you would also copy the libraries directory.

Of course, in doing this, you will have all dependencies from all of your projects available to the single project, but does it matter? It’s not like this is a 5GB node_modules directory (I exaggerate a little).

Now, the next step is likely going to take the most time. In your project, you must re-define where you fetch your autoload.php file from. In many cases, you can find/replace /autoload.php to /../autoload.php, but you should definitely be careful when doing this depending on what your build expects.

The other big change is if you run any commands from the vendor directory, you will have to redefine where they run from {project}/vendor/bin/xyz to vendor/bin/xyz.

Is that it? Well, yeah. The generated autoloader takes care of everything else, and there’s no funky hackery. Of course, yes the autoloader will have references to your other projects’ namespaces, but your individual projects should not be calling other projects’ namespaces.

Another bonus from using this method in development is you can relatively easily run all of your projects from a single set of containers instead of a set for each project.

Don’t forget to commit your composer.lock file! This ensures dependency consistency between development, CI/testing, and production!

Updating dependencies is relatively simple now.

  • composer outdated
  • composer update {whatever you decide to}
  • commit new lock file

You may want to document where certain dependencies are used, so maybe create a DEPENDENCIES.md when you start copying dependencies into the new composer.json.

How do you rate this article?

1


baohx2000
baohx2000

Just this guy, you know? PHP developer for 20+ years. Seen some shit.


Dev Thoughts
Dev Thoughts

Just some guy yammering about software development.

Send a $0.01 microtip in crypto to the author, and earn yourself as you read!

20% to author / 80% to me.
We pay the tips from our rewards pool.