<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/one-version-per-process-snvjy0ikw" -->

---
title: One version per process | daily.dev
description: Sebastian Bergmann argues that PHP&#x27;s small dependency trees, compared to npm&#x27;s sprawling ones, come down to the module resolver rather than culture or a...
canonical: https://daily.dev/posts/one-version-per-process-snvjy0ikw
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: One version per process | daily.dev
og:description: Sebastian Bergmann argues that PHP&#x27;s small dependency trees, compared to npm&#x27;s sprawling ones, come down to the module resolver rather than culture or a...
og:url: https://daily.dev/posts/one-version-per-process-snvjy0ikw
og:image: https://api.daily.dev/og/posts/SNVjY0iKW.png
og:image:alt: One version per process
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# One version per process

**[PHPUnit Expert](https://daily.dev/sources/phpunit-expert)** · 10 min read · 2 upvotes · 0 comments

## Summary

Sebastian Bergmann argues that PHP's small dependency trees, compared to npm's sprawling ones, come down to the module resolver rather than culture or a missing standard library. Because the PHP engine allows only one definition of a class name per process, Composer must resolve to a single version of every package, producing conflict errors that pressure maintainers toward short dependency lists and wide version ranges—mirroring Ruby's Bundler. npm, by contrast, resolves by file path and can silently duplicate incompatible versions, making dependencies costless to add. This explains why PHPUnit's PHAR distribution, which bundles a fixed dependency set into an unknown host process, must use php-scoper to rename namespaces (e.g. turning SebastianBergmann\Exporter into PHPUnitPHAR\SebastianBergmann\Exporter) to avoid class collisions—work that took over three years and shipped starting with PHPUnit 8.3 in August 2019. The piece also traces historical PHPUnit dependency conflicts (symfony/yaml, phpspec/prophecy, sebastian/diff vs phpcov) and PHPUnit's manifest/SBOM tooling for disclosing bundled dependencies.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://phpunit.expert/articles/one-version-per-process.html>

## Questions this post answers

### why does PHPUnit's PHAR distribution rename its bundled dependencies with php-scoper

Because a PHAR loads a fixed set of dependencies into a host process whose own dependency tree it has never seen, and PHP allows only one definition of a class name per process, so a colliding class from the host project could break or shadow PHPUnit's bundled copy. Since PHPUnit 8.3 (August 2019), php-scoper prefixes bundled namespaces, e.g. turning SebastianBergmann\Exporter into PHPUnitPHAR\SebastianBergmann\Exporter, so two versions of the same library can coexist as distinct classes to the engine.

_daily.dev surfaces writeups like this for developers debugging namespace collisions in bundled PHP tools._

### why do npm dependency trees end up much larger than PHP dependency trees for comparable projects

npm's resolver duplicates packages by file path in nested node_modules directories, so two packages needing incompatible versions of a shared dependency each get their own copy silently, making a new dependency cost nothing to add. PHP's Composer must resolve exactly one version of every package per project because the engine allows only one class definition per name per process, so conflicts surface as install failures instead of silent duplication, keeping PHP trees to dozens of packages versus hundreds in comparable JavaScript apps.

_understanding resolver design helps developers weigh dependency choices across ecosystems on daily.dev._

### what caused the PHPUnit GuzzleHttp fatal error when running a globally installed PHPUnit PHAR

A globally installed PHPUnit PHAR loaded its own bundled GuzzleHttp\Client classes before the project's, so when the project later needed GuzzleHttp\ClientInterface, it no longer matched the already-loaded Client class, and the test suite died with a fatal error. This was reported in issue #2014 in December 2015, and Jordi Boggiano's suggestion to use php-scoper eventually led to PHPUnit's PHAR namespace-prefixing build process.

_developers hitting PHAR class collisions can track fixes like this through daily.dev._

## Similar posts on daily.dev

- [Dependency Hygiene](https://daily.dev/posts/dependency-hygiene-wk2t5wl36) · stitcher.io · 23 upvotes · 0 comments
- [Your composer.lock knows what a carmaker only guesses](https://daily.dev/posts/your-composer-lock-knows-what-a-carmaker-only-guesses-hkhiruay4) · PHPUnit Expert · 0 upvotes · 0 comments
- [Continuing to move PHP Open Source Forward](https://daily.dev/posts/continuing-to-move-php-open-source-forward-kolebhiow) · JetBrains · 7 upvotes · 0 comments
- [\[PRE-RFC\] Runtime modules](https://daily.dev/posts/pre-rfc-runtime-modules-byx0xa1to) · externals.io · 0 upvotes · 0 comments

---

Tags: [#php](https://daily.dev/tags/php), [#npm](https://daily.dev/tags/npm), [#phpunit](https://daily.dev/tags/phpunit)

[View this post on daily.dev](https://daily.dev/posts/one-version-per-process-snvjy0ikw)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"One version per process","url":"https://daily.dev/posts/one-version-per-process-snvjy0ikw","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/one-version-per-process-snvjy0ikw"},"datePublished":"2026-09-30T03:42:07.670Z","dateModified":"2026-09-30T03:42:35.773Z","description":"Sebastian Bergmann argues that PHP's small dependency trees, compared to npm's sprawling ones, come down to the module resolver rather than culture or a...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/570a169d21c75adc71aac0978a20ce9f?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/570a169d21c75adc71aac0978a20ce9f?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"PHPUnit Expert","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"PHPUnit Expert","logo":"https://media.daily.dev/image/upload/s--FMZoysoG--/f_auto,q_auto/v1774964057/logos/phpunit-expert?_a=BAMAMiWQ0","url":"https://daily.dev/sources/phpunit-expert"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/one-version-per-process-snvjy0ikw","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"php,npm,phpunit","timeRequired":"PT10M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"PHPUnit Expert","item":"https://daily.dev/sources/phpunit-expert"},{"@type":"ListItem","position":3,"name":"One version per process"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/one-version-per-process-snvjy0ikw#faq","mainEntity":[{"@type":"Question","name":"why does PHPUnit's PHAR distribution rename its bundled dependencies with php-scoper","acceptedAnswer":{"@type":"Answer","text":"Because a PHAR loads a fixed set of dependencies into a host process whose own dependency tree it has never seen, and PHP allows only one definition of a class name per process, so a colliding class from the host project could break or shadow PHPUnit's bundled copy. Since PHPUnit 8.3 (August 2019), php-scoper prefixes bundled namespaces, e.g. turning SebastianBergmann\\Exporter into PHPUnitPHAR\\SebastianBergmann\\Exporter, so two versions of the same library can coexist as distinct classes to the engine. daily.dev surfaces writeups like this for developers debugging namespace collisions in bundled PHP tools."}},{"@type":"Question","name":"why do npm dependency trees end up much larger than PHP dependency trees for comparable projects","acceptedAnswer":{"@type":"Answer","text":"npm's resolver duplicates packages by file path in nested node_modules directories, so two packages needing incompatible versions of a shared dependency each get their own copy silently, making a new dependency cost nothing to add. PHP's Composer must resolve exactly one version of every package per project because the engine allows only one class definition per name per process, so conflicts surface as install failures instead of silent duplication, keeping PHP trees to dozens of packages versus hundreds in comparable JavaScript apps. understanding resolver design helps developers weigh dependency choices across ecosystems on daily.dev."}},{"@type":"Question","name":"what caused the PHPUnit GuzzleHttp fatal error when running a globally installed PHPUnit PHAR","acceptedAnswer":{"@type":"Answer","text":"A globally installed PHPUnit PHAR loaded its own bundled GuzzleHttp\\Client classes before the project's, so when the project later needed GuzzleHttp\\ClientInterface, it no longer matched the already-loaded Client class, and the test suite died with a fatal error. This was reported in issue #2014 in December 2015, and Jordi Boggiano's suggestion to use php-scoper eventually led to PHPUnit's PHAR namespace-prefixing build process. developers hitting PHAR class collisions can track fixes like this through daily.dev."}}]}
```

