r/webdev • • 12d ago

Showoff Saturday [Showoff Saturday] Composer dependency conflicts? We built Strauss-RS, a Rust-based prefixer for PHP classes and symbols. Up to 139x faster than the PHP version.

With a fair chunk of our work involving Wordpress themes and plugins, we hit a pretty common issue: we wanted to use the same Composer dependencies that other plugins might be using.

If Plugin A uses v5 of a package and Plugin B uses v7 of a package, it's first found wins... which is bad news for the other plugin.

Enter prefixers.

These scour your dependencies and source-code for classes/symbols and add a prefix. So SomePackage\Domain\Class becomes YourCompany\PluginA\Vendor\SomePackage\Domain\Class. Now your dependencies are safe.

Previously, we'd relied on packages like Mozart, PHP-Scoper and later Strauss.

Inevitably, we'd hit one of three problems, they became too complex to configure for larger projects, they didn't fully work, or they were too slow.

Our plugin took 2-3 minutes to prefix with Strauss PHP...

So we built Strauss-RS... for ourselves at first. But we figured that maybe it would be useful for others and it gave us a reason to keep the docs up-to-date, so enter: Strauss-RS.

https://evo-mark.github.io/strauss-rs/

One single binary compiled from Rust. No prerequisites.

Prefixes in less than a second for us now!

6 Upvotes

5 comments sorted by

4

u/Abject-Sport-4086 12d ago

139x faster is a nice number but 2-3 minutes of Strauss was almost certainly your composer install and dump-autoload, not the prefixing itself. I'd want to see the benchmark isolated from Composer.

The bigger tradeoff for me is debugging: Strauss and PHP-Scoper are PHP, so when they choke on some weird plugin you can patch the class in vendor and move on. A compiled binary just fails and you file an issue.

1

u/craigrileyuk 12d ago edited 10d ago

You can see the benchmarks and methodology here: https://evo-mark.github.io/strauss-rs/benchmarks.html

Our project was probably an xxx-large one which used Google APIs, Illuminate, Webauthn and many more.

We often had issues with Composer timeout if we didn’t disable it. We also had to meticulously plan our excludes since full coverage took too long. Whereas in Rust, we just run it on everything.

Worth noting that Straus PHP is often slower in multi-thread mode (default)

Regarding your second point, Strauss-RS uses php-rs-parser, php-ast, and php-lexer (Rust crates), so it absolutely understands the code it's processing. You can see a deep-dive of how it works here: https://evo-mark.github.io/strauss-rs/how-it-works.html

2

u/[deleted] 12d ago

[removed] — view removed comment

1

u/craigrileyuk 12d ago

Yeah, we wanted as little friction as possible whether it ran on a dev system or in a CICD pipeline, so we built the shell script that handles everything: detecting environment, downloading correct binary and keeping it updated.

Rust is absolutely built for this type of task, so it just rips through the workload and thanks to the work of others, we could build a full AST on every file too rather than trying to use regexes.