Copy-Based Framework: Own Your Code
Most PHP frameworks are installed as Composer packages. They end up in vendor/, behind a version constraint, and you interact with them through their public API. You don't read the implementation. You don't modify it. When something breaks after an update, you adapt your code to match the new version.
Phexium takes a different approach. The framework source code is placed in your project's src/ directory. When you clone Phexium, you get the full framework as part of your codebase, not as an external dependency.
The Problem With vendor/
Package managers solve real problems: versioning, dependency resolution, reproducible installs. But they also create a specific relationship between your code and the framework code. The framework is someone else's code that you consume.
This means:
- You can't change default behaviors without forking the package.
- You can't step through framework code as naturally as your own.
- Updates happen on the package maintainer's schedule, and you either accept the whole release or stay pinned.
- When the package is abandoned, you're stuck with a frozen dependency.
For large, general-purpose frameworks with hundreds of contributors, this trade-off makes sense. For a focused, educational framework like Phexium, it doesn't.
How Copy-Based Distribution Works
The src/ directory contains the Phexium framework core under the Phexium namespace:
src/
├── Application/ # Command/Query/Event base classes
├── Domain/ # Domain layer interfaces
├── Plugin/ # Port & Adapter implementations
└── Presentation/ # Presentation layer interfaces
Your application code is in app/ with its own namespace. The boundary between framework and application is a namespace boundary, not a filesystem wall.
When Phexium publishes updates, you pull them through Git:
git remote add upstream https://gitlab.com/phexium/framework.git
git fetch upstream
# See what changed in the framework
git diff HEAD upstream/trunk -- src/
# Pick what you want
git cherry-pick <commit-hash>
You review upstream changes against your local modifications. You accept what helps. You skip what doesn't. Every decision is explicit.
What This Enables
Direct modification. If a base class doesn't fit your domain, you change it. No fork, no PR to an upstream repository, no waiting for a release cycle. The change is local to your project and tracked in your Git history.
Full debugging transparency. Your IDE treats src/ like any other source directory. Breakpoints, type inference, refactoring tools, everything works without special configuration for vendor packages.
Selective updates. Framework updates are Git operations, not Composer constraints. You can cherry-pick a single bug fix from upstream without pulling in unrelated changes. You can diff your modifications against the new version before merging.
No abandonment risk. If Phexium development stops, your project is unaffected. The code is yours. There is no dependency pointing to a dead repository.
The Trade-offs
This model shifts responsibility to you. When you modify framework code, you need to understand what you're changing. When you merge upstream updates, you handle the conflicts.
This is intentional. Phexium is designed as a learning tool and reference implementation. The framework code is meant to be read, studied, and adapted. Treating it as a black-box dependency would defeat that purpose.
Copy-based distribution also means you don't benefit from Composer's automated dependency resolution for the framework itself. Phexium's third-party dependencies are still managed through Composer normally, but the framework layer is yours to manage.
When to Modify src/
Modify framework code when the default behavior doesn't match your domain requirements, when you need to add cross-cutting concerns to base classes, or when you want to experiment with architectural variations.
Don't modify src/ when the change belongs in application code (app/), when composition or configuration can achieve the same goal, or when the modification would break existing patterns without a clear benefit.
The distinction is straightforward: src/ defines the rules of the architecture. app/ plays by those rules. Change the rules only when you have a reason to.