Monorepo with PHP: Code Organization for Large Projects

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Monorepo with PHP: organizing code for scalable projects



In modern PHP development, code management becomes critically important as a project grows. The traditional approach with many separate repositories (polyrepo) often leads to synchronization issues, dependency duplication, and complexities during refactoring. An alternative is a monorepo, where all code, including libraries, utilities, and applications, is stored in a single Git repository. In this article, we will break down how to organize a monorepo in PHP using Composer, Git, and modern tools.



What is a monorepo and why is it needed in PHP?



A monorepo is a single repository containing several projects, packages, or modules that can be logically related. For a PHP developer, this means the framework core, API service, admin panel, and shared libraries are all in one folder.



Advantages of a monorepo:

  • Single versioning: All changes are committed atomically — a fix in a library is immediately accompanied by an update to the code that uses it.
  • Simplified refactoring: You can change a package's interface and immediately update all dependencies within a single pull request.
  • Tool standardization: One composer.json file for the root, unified linting and testing rules.
  • Reduced duplication: Common code (e.g., DTOs, helpers) does not need to be published to Packagist — it is available directly.


However, a monorepo requires discipline: without a clear structure, it turns into a "dump." The key to success is proper organization using Composer autoload and Git submodules or specialized tools like monorepo-builder.



Monorepo structure in PHP



Let's consider a typical structure for a PHP project that has several libraries and one application. The root directory looks like this:



my-monorepo/├── packages/│   ├── core/                  # Base library (core)│   │   ├── src/│   │   │   └── CoreClass.php│   │   └── composer.json│   ├── logger/                # Logging│   │   ├── src/│   │   └── composer.json│   └── api/                   # API package (can be a separate application)│       ├── src/│       │   └── Controllers/│       └── composer.json├── apps/│   └── web/                   # Web application (Laravel/Symfony)│       ├── public/│       ├── src/│       └── composer.json├── composer.json              # Root composer.json (meta-package)├── phpunit.xml.dist└── .gitignore


Organization principles:

  • All packages are in packages/. Each package has its own composer.json with a unique name (e.g., mycompany/core).
  • Applications (frontend, admin panel, API) are in apps/. They can use packages from packages/ as local dependencies.
  • The root composer.json serves for global configuration (autoload, scripts).


Setting up Composer for working with a monorepo



To make PHP autoloading work without publishing packages to Packagist, we use path repositories. Add the following block to the root composer.json:



{    "name": "mycompany/monorepo",    "require": {        "mycompany/core": "*",        "mycompany/logger": "*"    },    "repositories": [        {            "type": "path",            "url": "packages/*"        }    ],    "autoload": {        "psr-4": {            "MyCompany\\\\": "packages/"        }    },    "scripts": {        "test": "phpunit"    }}


Now when running composer install, Comp

Blogs

Book Recommendations