Do you take care of 2 or more projects on the same framework? Do you upgrade them both to the newest version of the framework from time to time? Or maybe you're successful, you grow and have 10 such projects.
Today I'll show you how to maintain speed while keeping the maintenance cost low.
This idea came originally from tech companies with large code bases and constantly growing products:
Yet I never saw such approach in PHP world. I mean, I saw monoliths but never a monorepo. Last year I started to work more closely with Shopsys and they did one crazy thing - put the sandbox project into monorepo repository. Instead of having 2 repositories - monorepo and showcase project separated - like Symfony and its Demo, Laravel, CakePHP, Nette or basically any PHP framework I've seen, ** it is just one**.
I had my concerns about how packages and project development will go together, but now I see it was just fear from unknown.
The fuel for the next step of this approach came last month. I'm currently working on 2 open-sourced Symfony Application (non CLI!) - open-training and open-real-estate.
How are 2 Symfony applications different? Well, the entities, repository queries, templates, design and controller actions are unique. But the PHP, used framework, bundle integration, database type, deploy strategy, own packages that auto-discover entities for example, Kernel boilerplate are the same.
One example from last month for all: I made first package that extends EasyAdminBundle (you can read it here/) for open-lecture project. Of course, I need that for open-real-estate. Now I had to create a package, make own Github repository, register it on packagist, add it to other project and somehow switch between them for every update... ugh, I guess you would not want to pay me for this bureaucracy.
Since I'm the main investor of myself and I hate wasting time on repetitive work, I decided to give myself a question:
"Is possible to maintain 2 Symfony applications in a single repository with ease?"
Google answered by miss-leading Multiple Kernels for one Project..
The goal is to run...
*/bin/console server:run
...and have a running website, with own database, own code and also no duplicated code.
After a few hours of playing with the code (my favorite game), I started to see light in the darkness. I'm right in the very start of using this architecture, so you'll have the knowledge in the rawest form of fresh experience. It's easier to explain and understand, rather than something I do daily for the last 5 years and don't have to think about it.
If there are 2 App\Kernel
classes the application would break. Pick a name that is specific for the project - here it's "OpenTraining" - and rename it in namespace App\
, namespace App;
, use App\
in PHP code. Don't forget the composer.json
as well.
-namespace App;
+namespace OpenTraining;
use Symfony\Component\HttpKernel\Kernel;
-final class AppKernel extends Kernel
+final class OpenTraining extends Kernel
{
// ...
}
And let composer know, what we did:
composer dump-autoload
bin/console
-require __DIR__ . '/vendor/autoload.php';
+require getcwd() .'/vendor/autoload.php';
// ...
public/index.php
This is more complicated because the working directory is not in the monorepo root but in the project root (/projects/open-training
):
-require __DIR__ . '/../vendor/autoload.php';
+$possibleAutoloadFiles = [
+ // project
+ __DIR__ .'/../vendor/autoload.php',
+ // monorepo
+ __DIR__ .'/../../../vendor/autoload.php',
+];
+foreach ($possibleAutoloadFiles as $possibleAutoloadFile) {
+ if (file_exists($possibleAutoloadFile)) {
+ require $possibleAutoloadFile;
+ }
+}
Instead of the old root as you're used to...
bin/console server:run
...you have to use projects' bin/console
:
projects/open-training/bin/console server:run
projects/open-real-estate/bin/console server:run
You might be already wondering How do manage composer dependencies in all this madness? Well, you have to update projects' composer.json
separately, pick them all and copy to root composer.json
. And be careful not to use different versions, it might cause the project to work on monorepo but fail after deploying on production...
Just kidding, you know I'm too lazy for this
All you need to do is to update projects' composer.json
. For the rest, there is a tool I use to manage Symplify monorepo dependencies that does the dirty work for you:
composer require symplify/monorepo-builder
# make sure there is the same version for every package in every composer.json
vendor/bin/monorepo-builder validate
# merge "require", "require-dev", "autoload" and "autoload-dev" from projects to the root composer.json
vendor/bin/monorepo-builder merge
# to make sure we have installed projects' dependencies
composer update
In the root composer.json
you might want to add some coding standards and static analysis, that is not in projects.
See the monorepo-builder.yml
config in my open-project monorepo repository to get the idea how to configure it.
And that's it! We have now one repository to maintain, no matter how many projects we have, and we met our goals:
Now we can run application with projects/open-training/bin/console server:run
and it works!
Happy maintaining!
Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors.
I'd really appreciate it!