piedweb / composer-symlink
One Vendor Directory for multiple PHP projects using same dependencies - Disk space efficient composer fixer.
Requires
- php: >=8.4
- symfony/filesystem: ^7|^8
- thecodingmachine/safe: ^3
This package is auto-updated.
Last update: 2026-08-14 12:25:14 UTC
README
Composer Symlink
Disk efficient composer (fixer || symlinker) ➜ multiple projects relying on the same package version, why having multiple copies ?
This is a duplicate code killer to win some disk space.
Each vendor/{vendor}/{package} directory is moved to a shared
{globalVendorDir}/{vendor}/{package}-{version} and replaced by a symlink. Projects locking the
same version end up sharing a single copy; projects locking different versions keep their own.
Install
Via Packagist
Create a new project and install the dependency
mkdir composer-dependencies && cd composer-dependencies composer require piedweb/composer-symlink
Usage
Create cs.php
<?php use PiedWeb\ComposerSymlink\ComposerSymlink; include 'vendor/autoload.php'; (new ComposerSymlink( [ '/path/to/my/project', '/path/to/my/second/project', ], '/path/to/composer-dependencies/global-vendor' ))->exec();
Then run it after each composer update:
php cs.php
You can also wire it in the post-update-cmd script of each project:
{
"scripts": {
"post-update-cmd": "php /path/to/composer-dependencies/cs.php"
}
}
Arguments
| Argument | Description |
|---|---|
$projectPathList |
Every project sharing the global vendor directory. Each one needs a vendor/ directory and a composer.lock. |
$globalVendorDir |
Absolute path of the shared vendor directory. Created on demand. A relative path throws. |
$pruneUnused |
true by default: shared packages no longer referenced are deleted. |
The project list must be exhaustive
Pruning is what reclaims the disk space, and it decides what to delete from the global vendor
directory alone — it has no way to discover a project you did not list. Leaving a project out
deletes the packages it still symlinks, and its vendor/ is left with dangling links until the
next composer install.
So either list every project sharing the directory, or pass pruneUnused: false and clean up
by hand:
(new ComposerSymlink([$projectPath], '/path/to/global-vendor', pruneUnused: false))->exec();
What is left alone
vendor/composer/,vendor/bin/and the files at the root ofvendor/.- Packages absent from
composer.lock(path repositories, hand-vendored code): without a version there is nothing to key a shared copy on. - Packages already symlinked. The link target wins over
composer.lock, so a lock bumped without a matchingcomposer installnever invalidates the copy the project actually uses.
Contributing
Please see contributing
Credits
License
The MIT License (MIT). Please see License File for more information.