phpDocumentor / phpDocumentor/guides
Split packages do not declare the dependencies their code uses
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 36
- Forks
- 22
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 24
Description
Summary
Every split package declares fewer dependencies than its production code uses. The classes resolve inside the monorepo because the root pulls everything in together, but each packages/* directory is published as a standalone Composer package, and there the metadata is simply wrong. A consumer installing phpdocumentor/filesystem on its own gets a FlySystemAdapter whose League\Flysystem and Flyfinder classes are not required by anything.
I ran composer-require-checker against each split package in isolation — package directory copied out, its own composer install, autoload-dev and require-dev removed so only production code is scanned. Unknown symbols per package:
| Package | unknown symbols |
|---|---|
| phpdocumentor/guides | 26 |
| phpdocumentor/guides-restructured-text | 28 |
| phpdocumentor/filesystem | 16 |
| phpdocumentor/guides-cli | 16 |
| phpdocumentor/guides-graphs | 12 |
| phpdocumentor/guides-code | 11 |
| phpdocumentor/guides-theme-rst | 11 |
| phpdocumentor/guides-markdown | 10 |
| phpdocumentor/dev-server | 8 |
| phpdocumentor/guides-theme-bootstrap | 6 |
psr/log is already handled separately in #1354 and no longer appears in these numbers.
Straightforward part
Most of it is a plain missing require for a library the code uses directly and that is already installed transitively. Nothing optional, no semantics involved:
| Package | missing |
|---|---|
| filesystem | league/flysystem, phpdocumentor/flyfinder |
| guides | psr/container, psr/clock, league/uri-interfaces, symfony/http-client-contracts |
| guides-cli | psr/container, psr/clock, psr/event-dispatcher, doctrine/deprecations, league/tactician, phpdocumentor/flyfinder, phpdocumentor/filesystem, webmozart/assert |
| guides-code | twig/twig |
| guides-graphs | symfony/http-client-contracts, webmozart/assert |
| guides-markdown | symfony/string |
| guides-restructured-text | symfony/string, phpdocumentor/flyfinder |
| guides-theme-rst | twig/twig, symfony/polyfill-mbstring |
| dev-server | guzzlehttp/psr7, ratchet/rfc6455, react/event-loop, psr/event-dispatcher, symfony/event-dispatcher-contracts |
Four things that need a decision, not a declaration
symfony/config and symfony/dependency-injection are used by the DependencyInjection classes of guides, guides-code, guides-graphs, guides-markdown, guides-restructured-text, guides-theme-bootstrap and guides-theme-rst. Declaring them as require forces every consumer to install Symfony's container even when wiring the library by hand. That is a product decision: hard requirement, suggest, or the extension classes move somewhere optional.
A dependency cycle. guides-restructured-text/src references phpDocumentor\Guides\Bootstrap\* in TabDirective.php, TabsNode.php and TabNode.php, while guides-theme-bootstrap requires guides-restructured-text. Declaring this one would close a ring, so it needs a code change rather than metadata.
Test support in production src. packages/guides/src/DependencyInjection/TestExtension.php uses Monolog\Logger, Monolog\Handler\TestHandler and Symfony\Component\Clock\MockClock. It is test infrastructure whose only user is tests/ApplicationTestCase.php, and it reached this package through a package merge rather than a decision. Moving it out of the shipped source, rather than making Monolog a production dependency, is #1357.
PHP extensions. ext-filter, ext-ctype and ext-mbstring are used but never declared.
Symbols like UnknownSymbol, template and templateArray come from docblock generics and belong in composer-require-checker.json, not in require.
One that reads worse than it is
guides-theme-rst calls mb_str_pad() in RstTheme/Twig/RstExtension.php:154, which PHP provides only from 8.3 while the package declares php: ^8.1. It is not broken: symfony/polyfill-mbstring has supplied the function since v1.28 and arrives through symfony/string and twig/twig. Verified by installing the package standalone in a PHP 8.1 container, where mb_str_pad() returns the padded string. So this is the same undeclared-dependency case as the rest and not a version-floor decision; #1356 declares the polyfill.
Why CI does not catch this
The repository already ships composer-require-checker.json and pins the tool in .phive/phars.xml, and phpDocumentor has a reusable workflow for exactly this — phpDocumentor/.github/.github/workflows/dependency-analysis.yml, which runs composer-require-checker check --config-file=$(pwd)/composer-require-checker.json. Wiring that in as-is would not help, for two reasons I measured:
The workflow checks the root composer.json, and this root has no production autoload at all, only autoload-dev pointing at test directories. Running the checker there reports There were no symbols found, please check your configuration. — a green no-op. Catching the gaps above needs the check to run per package, over packages/*/composer.json.
The pinned composer-require-checker 3.5.1 aborts on symfony/config v8 with Syntax error, unexpected '(', expecting T_VARIABLE in Definition/Builder/NodeBuilder.php, and --ignore-parse-errors does not help. It hits guides-cli, the only package declaring symfony/config directly, and the abort produces empty output that reads like a pass. The numbers in this issue were produced with composer-require-checker 4.24.0.
Expected output
Each published package declares the dependencies its own source code uses, and CI fails when that stops being true.
#1356 implements the straightforward table above and #1357 moves TestExtension. The remaining decision items are left here for maintainers.
Assisted by claude-code:claude-fable-5 — Session
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the composer.json files under packages/* and the existing composer-require-checker.json; compare each package's production source with its declared dependencies. Read .phive/phars.xml and phpDocumentor/.github/.github/workflows/dependency-analysis.yml to understand the checker and workflow. Done means package metadata is accurate and CI checks each published package without masking parse failures, while the listed decision items are resolved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- build-system, ci-cd
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100