How to Develop Multiple Symfony Applications Fast while Keeping the Quality

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.

I hate Repetitive Work, I Hate Repetitive Work

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.

4 Steps to turn Single-Repository project to Parts of Monorepo

1. App Unique Namespace

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.

PHPStorm → Replace in path
-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

2. Fix Autoload Paths

-require __DIR__ . '/vendor/autoload.php';
+require getcwd() .'/vendor/autoload.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;
+    }

3. Bin\Console

Instead of the old root as you're used to...

bin/console server:run have to use projects' bin/console:

projects/open-training/bin/console server:run
projects/open-real-estate/bin/console server:run

4. Merge Projects Composer

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!