Monorepository with PHP: Organizing Code in Practice

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

Introduction: Why a Monorepository in PHP?

\n

Developing complex PHP applications (CRM, SaaS, frameworks) often faces a dilemma: store all code in one repository (monorepository) or split it into many microservices and packages. A monorepo with PHP is a strategy where several related projects (libraries, modules, applications) reside in a single Git repository.

\n

In practice, this provides:

\n
    \n
  • Unified change history — easier to track how a change in one package affected another.
  • \n
  • Simplified CI/CD setup — a single pipeline for building and testing all components.
  • \n
  • Reduced duplication — shared configs, scripts, and dependencies.
  • \n
  • Fast iteration — no need to publish packages to Packagist for local testing.
  • \n
\n

However, a monorepository requires discipline. Without proper code organization, it turns into a "spaghetti monolith." In this article, we will cover practical steps for organizing a PHP project in a monorepository.

\n\n

1. Monorepository Directory Structure

\n

The key principle is a clear separation into packages and applications. Let's look at a typical architecture example:

\n\n
project-root/\n├── apps/\n│   ├── web/                  # Web application (Symfony/Laravel)\n│   │   ├── public/\n│   │   ├── src/\n│   │   └── composer.json\n│   └── api/                  # API application (Lumen/Slim)\n│       ├── public/\n│       ├── src/\n│       └── composer.json\n├── packages/\n│   ├── core/                 # Core (common interfaces, entities)\n│   │   ├── src/\n│   │   └── composer.json\n│   ├── billing/              # Billing module\n│   │   ├── src/\n│   │   └── composer.json\n│   └── notifications/        # Notifications module\n│       ├── src/\n│       └── composer.json\n├── tools/                    # Development scripts (PHP-CS-Fixer, PHPStan)\n├── composer.json             # Root manifest\n└── phpstan.neon              # Common analysis settings
\n\n

Note: each package in packages/ is a full-fledged Composer library with its own composer.json. This allows isolating dependencies and testing them separately.

\n\n

Example composer.json for the core package

\n
{\n    "name": "myproject/core",\n    "type": "library",\n    "autoload": {\n        "psr-4": {\n            "MyProject\\\\Core\\\\": "src/"\n        }\n    },\n    "require": {\n        "php": ">=8.1"\n    },\n    "extra": {\n        "branch-alias": {\n            "dev-main": "1.x-dev"\n        }\n    }\n}
\n\n

2. Managing Dependencies with Composer

\n

The root composer.json is the heart of the monorepository. It contains:

\n
    \n
  • A list of all locally connected packages (via path repository).
  • \n
  • Dev dependencies for the entire project (PHPUnit, PHPStan, GrumPHP).
  • \n
  • Automation scripts (static analysis, tests).
  • \n
\n\n

Example root composer.json

\n
{\n    "name": "myproject/monorepo",\n    "type": "project",\n    "repositories": [\n        {\n            "type": "path",\n            "url": "packages/*",\n            "options": {\n                "symlink": true\n            }\n        }\n    ],\n    "require": {\n        "myproject/core": "@dev",\n        "myproject/billing": "@dev",\n        "myproject/notifications": "@dev"\n    },\n    "require-dev": {\n        "phpunit/phpunit": "^10.0",\n        "phpstan/phpstan": "^1.10",\n        "friendsofphp/php-cs-fixer": "^3.0"\n    },\n    "autoload": {\n        "psr-4": {\n            "MyProject\\\\": "packages/*/src/"\n        }\n    },\n    "scripts": {\n        "test": "phpunit",\n        "analyse": "phpstan analyse",\n        "cs-fix": "php-cs-fixer fix"\n    }\n}
\n\n

Key points:

\n
    \n
  • symlink: true

Blogs

Book Recommendations