rinvex/laravel-composer
Rinvex Composer integrates Composer package management into Laravel, letting you inspect installed packages and run dependency operations programmatically from your application. A lightweight wrapper for managing Composer workflows without leaving PHP.
Prerequisites: Composer 2.x and PHP 7.4+ (verify exact bounds in the plugin's composer.json — the package is archived and predates PHP 8.2/Composer 2.5 in places). Because it is a Composer plugin, it must be a real dependency of the root project (or installed globally) — it will never run from a transitive dependency.
1. Require the plugin
composer require --dev rinvex/laravel-composer
Use --dev unless your install-time scripts are required in production images.
2. Allow it to execute (Composer 2.2+ mandatory)
Without this Composer blocks the plugin and it silently does nothing:
{
"config": {
"allow-plugins": {
"rinvex/laravel-composer": true
}
}
}
3. Look here first, in this order
src/ that implements Composer\Plugin\PluginInterface and EventSubscriberInterface — this is the authoritative list of events it listens to and the exact extra keys it reads. Since the repo is archived, the source is the documentation.README.md usage block for the extra schema.composer.json → extra.class, which tells you how the plugin is bootstrapped and therefore how to fork/subclass it.4. First use case — install a package outside vendor/
{
"extra": {
"rinvex-composer": {
"installer-paths": {
"modules/Blog": ["acme/blog-module"],
"modules/Shop": ["acme/shop-module"]
}
}
}
}
Then:
composer install -vvv
ls modules/Blog # should exist instead of vendor/acme/blog-module
If nothing happens, you have an allow-plugins, key-name, or event-matching problem — see Gotchas.
5. Second use case — lifecycle hooks
Attach shell work to post-install-cmd / post-update-cmd / post-uninstall-cmd so module wiring (asset publishing, cache clearing, .env scaffolding) happens automatically after a package moves in or out.
modules/The primary day-to-day leverage: keep first-party or vendored modules in a readable modules/ tree while still installing them via Composer (so composer.json, version constraints, and the lock file stay the source of truth). Combine with Laravel's package discovery by giving each module a composer.json with:
"extra": {
"laravel": { "providers": ["Acme\\Blog\\BlogServiceProvider"] }
}
Then register the module namespace in the root composer.json autoload, because Composer no longer owns that path:
"autoload": { "psr-4": { "Acme\\Blog\\": "modules/Blog/src/" } }
Treat the plugin's script support as a post-install orchestrator and keep it idempotent and non-interactive:
"scripts": {
"post-install-cmd": [
"@php artisan vendor:publish --tag=acme-config --force",
"@php artisan optimize:clear"
]
}
Rules that keep this sane:
post-install-cmd — use post-update-cmd gated on --no-dev, or better, an explicit Artisan command.test -f artisan if the project may install outside a Laravel root (CI scaffolding, tooling repos).@php over bare php so the Composer runtime's binary is reused.Composer plugins are skipped in --no-plugins mode. If your CI uses composer install --no-plugins --no-scripts for speed/security, modules will land in vendor/ and your build breaks. Either:
--no-plugins for the build stage and keep --no-scripts for untrusted contributions, ormodules/ tree and validate it separately.Document the choice in the CI file — this is the #1 thing that surprises teammates.
Pair the plugin with a path repository so you get a symlink into modules/ with live editing:
"repositories": [{ "type": "path", "url": "modules/Blog", "options": { "symlink": true } }]
This is the pattern that actually makes the package worth using — you get Composer's dependency resolution and a normal source tree in your IDE.
Because behavior is invisible until it isn't, add to README/CONTRIBUTING:
composer config allow-plugins.rinvex/laravel-composer truecomposer install (not --no-plugins)modules/ vs vendor/rm -rf modules vendor composer.lock && composer installTo customize behavior without patching an archived package:
"extra": { "class": "Your\\Plugin" } — wait, that key belongs to the plugin's own composer.json, so a fork is the only way to point at your class.scripts in your root composer.json, which you control and can version independently.Composer 2.2+ blocks unlisted plugins. Symptom: composer install succeeds, no module directory appears, no warning you'd notice among the output. Check:
composer diagnose
composer config --list | grep -i allow-plugins
11 stars and archived means: no security patches, no Composer 3 support, no PHP 8.3+ guarantees. Mitigate by pinning an exact version and committing the lock file:
"require-dev": { "rinvex/laravel-composer": "1.x.y" }
Consider vendoring the plugin into a private VCS repo or forking before the next Composer major.
Moving a package out of vendor/ does not add it to Composer's generated autoloader. You must either declare the namespace in root autoload.psr-4 or require a stub that require_onces the module's entry point. Forgetting this produces "Class not found" errors at runtime that look like a Laravel discovery failure — they aren't.
composer update can move or resurrect pathsIf a package is removed from installer-paths, the old directory may be orphaned (left behind) or overwritten. Don't hand-edit files inside modules/ for third-party code; treat those directories as install artifacts and .gitignore them, or convert them to path repositories if you intend to edit.
post-uninstall-cmd runs after the package files are gone on modern Composer. Any script that needs the package's code (e.g. a cleanup Artisan command defined by that very package) will fail. Capture what you need at pre-uninstall-cmd or make cleanup self-contained shell.
The plugin executes during Composer's lifecycle, not inside the framework. app(), facades, and Eloquent are unavailable unless your script boots artisan itself. Keep hook scripts to @php artisan ... invocations and filesystem/CWD-safe operations.
Relative paths in installer-paths and hook scripts resolve against the project root, but Windows separators and case-insensitivity bite. Standardize on forward slashes in config; verify on the CI matrix before trusting it.
composer install -vvv # every listener and its payload
composer install 2>&1 | grep -i plugin
cat vendor/composer/installed.json | jq '.[] | select(.name|test("rinvex"))'
If the plugin is listed in installed.json but no hook output appears, you have an extra key mismatch — diff your key against the literal string in the plugin source, not against blog posts.
The root composer.lock hashes the extra block of the root package in some Composer versions. Editing install paths without a subsequent composer update --lock can make installs non-reproducible across machines.
composer/installers is maintained, handles installer types idiomatically, and has broad ecosystem support. If your only need is moving one package to a custom path, prefer composer/installers or a path repository; use this plugin only where you specifically need its combined "custom path + lifecycle script" behavior, and be prepared to migrate off it.
How can I help you explore Laravel packages today?