As a PHP/Laravel composer package developer, you've likely faced this common dilemma: you're actively developing a package while simultaneously using it in your main application. You need to test changes immediately in your local environment, but in production, you want to use the stable, published version from Packagist. How do you manage this without constantly editing composer.json or maintaining separate configuration files?
The solution lies in Composer's global configuration combined with path repositories. This approach keeps your composer.json clean and production-ready while enabling seamless local development.
The Challenge
When developing a package within your main application, you typically have two conflicting needs:
- Local Development: You want Composer to symlink to your local package source so changes are immediately reflected
- Production Deployment: You want Composer to pull the stable release from Packagist
The traditional approach involves adding a path repository to your composer.json:
{
"repositories": [
{
"type": "path",
"url": "./packages/my-package"
}
],
"require": {
"vendor/my-package": "^1.0"
}
}
The problem? When you deploy to production, this path repository configuration remains in your composer.json. If the local path doesn't exist on the production server, Composer fails. Even if you .gitignore the packages directory, you're left with a broken configuration.
The Solution: Global Composer Configuration
Composer supports global configuration that applies to all projects on your machine. By moving your path repository configuration to the global level, you can keep your project's composer.json clean and production-ready.
Step 1: Clean Your Project's composer.json
First, ensure your composer.json uses the version constraint but no custom repositories:
{
"require": {
"iperamuna/laravel-self-deploy": "^1.0"
}
}
This configuration works perfectly in production—Composer will fetch version ^1.0 from Packagist.
Step 2: Configure Global Path Repository
On your local development machine only, run:
composer config --global repositories.my-package path /absolute/path/to/packages/my-package
For example, if you're developing the laravel-self-deploy package:
composer config --global repositories.laravel-self-deploy path /Users/username/projects/myapp/packages/iperamuna/laravel-self-deploy
This adds the path repository to your global Composer configuration file (~/.composer/config.json or ~/.config/composer/config.json on Linux/Mac).
Step 3: Verify the Configuration
You can view your global configuration:
composer config --global --list
Or inspect the configuration file directly:
cat ~/.composer/config.json
You should see something like:
{
"repositories": {
"laravel-self-deploy": {
"type": "path",
"url": "/Users/username/projects/myapp/packages/iperamuna/laravel-self-deploy"
}
}
}
Step 4: Install or Update
Now, when you run composer install or composer update on your local machine:
- Composer checks your global configuration
- Finds the path repository definition
- Symlinks the local package instead of downloading from Packagist
- Your changes are immediately available in your application
Step 5: Deploy to Production
On your production server:
- The global Composer configuration doesn't exist (or doesn't include your local path)
- Composer only sees the
"iperamuna/laravel-self-deploy": "^1.0"requirement - It fetches the package from Packagist as expected
- Everything works seamlessly
How It Works
Composer's dependency resolution follows this priority order:
- Project-level repositories (defined in your project's
composer.json) - Global repositories (defined in
~/.composer/config.json) - Packagist (the default public repository)
By defining path repositories globally, you're essentially saying: "On this machine, if you need this package, use this local path first." Production servers don't have this configuration, so they skip straight to Packagist.
Best Practices
1. Use Absolute Paths
Always use absolute paths in your global configuration to avoid ambiguity:
# Good
composer config --global repositories.my-package path /Users/username/projects/app/packages/my-package
# Avoid relative paths
composer config --global repositories.my-package path ./packages/my-package
2. Match Version Constraints
Ensure your package's version (whether in git tags or composer.json) satisfies the version constraint in your main application. For local development packages without explicit versions, Composer treats them as dev-main or dev-master.
If needed, you can alias the version:
{
"require": {
"vendor/my-package": "dev-main as 1.0.0"
}
}
3. Document the Setup
Add a note to your project's README for other developers:
## Local Package Development
If you're developing the `iperamuna/laravel-self-deploy` package locally, configure a global path repository:
\```bash
composer config --global repositories.laravel-self-deploy path /absolute/path/to/packages/iperamuna/laravel-self-deploy
\```
4. Keep composer.json Clean
Never commit path repositories to your project's composer.json. This ensures:
- Production deployments are clean and predictable
- New team members don't encounter path-related errors
- CI/CD pipelines work without modification
5. Team Coordination
If multiple developers work on the same package, each developer needs to:
- Clone the package to their preferred location
- Configure their own global path repository
- Use the same version constraint in
composer.json
Removing Global Configuration
If you later want to use the Packagist version even locally (e.g., for testing), simply remove the global configuration:
composer config --global --unset repositories.my-package
Then run:
composer update vendor/my-package
Composer will now fetch the package from Packagist.
Real-World Example: Laravel Self Deploy
Let's walk through a complete example using the iperamuna/laravel-self-deploy package:
Project Structure:
/Users/johndoe/projects/myapp/
├── app/
├── packages/
│ └── iperamuna/
│ └── laravel-self-deploy/ # Local package source
├── vendor/
└── composer.json
Step 1: Clean composer.json:
{
"require": {
"iperamuna/laravel-self-deploy": "^1.0"
}
}
Step 2: Configure globally (local machine only):
composer config --global repositories.laravel-self-deploy path /Users/johndoe/projects/myapp/packages/iperamuna/laravel-self-deploy
Step 3: Install dependencies:
composer install
Output:
- Installing iperamuna/laravel-self-deploy (1.0.0): Symlinking from /Users/johndoe/projects/myapp/packages/iperamuna/laravel-self-deploy
Step 4: Make changes to the package:
Edit files in packages/iperamuna/laravel-self-deploy/ and they're immediately available in your application.
Step 5: Deploy to production:
Your production server runs composer install and sees:
- Installing iperamuna/laravel-self-deploy (1.0.0): Downloading from packagist
Perfect! No configuration changes needed.
Benefits of This Approach
✅ Clean Version Control: composer.json stays production-ready
✅ Zero Production Impact: No accidental local path references
✅ Flexible Development: Easy to switch between local and Packagist versions
✅ Team-Friendly: Each developer configures their own environment
✅ CI/CD Compatible: Automated builds work without modification
Potential Pitfalls and Solutions
Pitfall 1: Forgetting to Configure Globally
Problem: You remove the path repository from composer.json but forget to add it globally.
Solution: Composer will use the Packagist version. Just configure globally and run composer update.
Pitfall 2: Different Package Locations per Developer
Problem: Team members have different directory structures.
Solution: This is actually a benefit! Each developer sets their own global path. Document the process in your README.
Pitfall 3: Stale Symlinks
Problem: You move or delete the local package directory but the global config still points to it.
Solution: Either update the path or remove the global configuration:
composer config --global --unset repositories.my-package
Conclusion
Managing both development and production versions of your Composer packages doesn't have to be painful. By leveraging Composer's global configuration for path repositories, you can:
- Keep your project's
composer.jsonclean and production-ready - Develop packages seamlessly alongside your main application
- Deploy to production without configuration changes
- Maintain flexibility across different development environments
This approach is particularly valuable for Laravel/PHP composer package maintainers, teams building private packages, or anyone who needs tight integration between package development and application code.
Give it a try on your next package, and enjoy a cleaner, more maintainable workflow!
Have you used Composer global configuration for package development? Share your experiences and tips in the comments below!