It was a typical Saturday night, coding away while the rest of the world was probably out having fun or sleeping. Yep, that's me, and if you're reading this, it's probably you too. I was deep into one of my side projects, building an open-source JavaScript tool (you'll probably see it soon, stay tuned! 😉), when I casually hit pnpm install
. And, whoosh! Installation completed instantly. No surprise there, right? That's pnpm doing its magic—sharing packages globally, optimizing storage, and making installs lightning fast.
Right after that, I switched gears to a Composer project (my little side project had both backend and frontend). Out of habit, I typed composer install
and waited. And waited some more. Composer downloaded everything fresh, even packages I've installed dozens of times. That's when the idea hit me, why doesn't Composer have pnpm's awesome feature?
Alert! 🚨 My brain had officially woken up.
If you know me, you know I love building things out of pure curiosity and the moment's need. Remember my previous adventures like ts-runtime-picker, or when I almost rewrote half of Laravel just to fix my personal issue? Yeah, I get carried away sometimes, but hey, it's all part of the fun (you should check out those stories sometime).
Anyway, back to my Saturday night realization. I decided right then and there, why not build "pnpm for Composer"? Wait, if npm has pnpm, then Composer should have… Pomposer! 😄 Yes, I actually loved that name and ran with it immediately.
Terminal opened:
mkdir pomposer && composer init
Then came the staring at my blank sketchpad. How do we make this happen?
First obvious idea: install packages once in a global folder, let's call it ~/.pomposer-store
. Easy right? I quickly tried creating a dedicated folder for each dependency, running composer require
individually:
cd ~/.pomposer-store
mkdir monolog/monolog && cd monolog/monolog
composer require monolog/monolog
Yay! Wait… no. 🤔 It installed sub-dependencies too. That's not what I wanted at all. Dependency hell was looming. And to be honest, it was frustrating. How do we get Composer to just install a single package?
I tried different strategies, maybe I could parse the composer.lock
file manually and selectively install dependencies? Nah, we don't always have the composer.lock. What about using Composer's internal APIs? Nope, Composer stubbornly refuses to install just one package without dragging along its friends.
Next idea: cloning repositories directly from GitHub? It sounded brilliant at first, but then I realized not every package was on GitHub :"), and managing different git references became messy super fast.
I spent several hours, probably way too many cups of coffee deep, contemplating how Composer works under the hood. After all that caffeine-fueled brainstorming, it finally struck me.. what if we just download packages directly from Packagist as ZIP files? Direct, simple, no Composer command involved:
$url = "https://repo.packagist.org/p2/monolog/monolog.json";
$json = json_decode(file_get_contents($url), true);
$zipUrl = $json['packages']['monolog/monolog'][0]['dist']['url'];
file_put_contents("monolog.zip", file_get_contents($zipUrl));
Wow! It worked perfectly. Packages downloaded separately, no dependency mess. But then, another problem—how do we handle autoloading? Composer generates nice autoload files for us, but now we're doing it manually.
Another round of brainstorming commenced. Regex parsing adventures, many error messages, and a lot more coffee later, the solution slowly emerged—I could read each package's composer.json
to generate a unified autoloader dynamically. It wasn't ideal, but it worked:
// autoload_psr4.php
return [
'Monolog\\' => '~/.pomposer-store/monolog/monolog/src/',
// other packages
];
And voilà! It worked. 😍 Here's a working example:
Create a simple project with a composer.json
:
{
"require": {
"monolog/monolog": "^3.9",
"hichemtab-tech/namecrement": "^1.1"
}
}
Run Pomposer:
pomposer install
And a quick test file:
<?php
require_once 'vendor/autoload.php';
use HichemTabTech\Namecrement\Namecrement;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$existing = ['file', 'file (1)', 'file (2)'];
$newName = Namecrement::namecrement('file', $existing);
echo "Next unique file name after the list of existing files:\n";
echo "Existing files: " . implode(', ', $existing) . "\n";
echo "New file name: " . $newName . "\n\n";
$log = new Logger('pomposer');
$log->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));
$log->info('Pomposer is alive! 🚀');
Running php index.php
outputs:
Next unique file name after the list of existing files:
Existing files: file, file (1), file (2)
New file name: file (3)
[2025-07-06 20:31:22] pomposer.INFO: Pomposer is alive! 🚀 []
Yes, Pomposer lives! But hold on, we're still in super-early beta. It has no Composer plugin support, no dev dependencies yet, no scripts or fancy features—it's just a proof-of-concept.
But hey, it works—and that's pretty cool, right? 😅
Building Pomposer has been a fantastic challenge filled with both frustrating and satisfying moments. It's not just another Composer wrapper—it's a completely new take on package management inspired by pnpm. And yes, it's rough, but that's the beauty of open source.
If this excites you and you'd like to see Pomposer evolve, contribute, or share ideas, please join us on GitHub. I can't wait to hear your thoughts and ideas!
Top comments (1)
Shouldn't that be
~/.pomposer-store/monolog/monolog/3.9/src/
? Otherwise there is no use for storing the different package versions in separate directories?The next question is how does pomposer handle multiple versions of the same package in a project? Isn't that why Composer has the dependency mess. You can't change the library namespace for the libraries that have different package versions.
While I think it is a great idea, I think there are some unmovable walls that are going to make it impossible to achieve what you have in mind in more challenging situations.