php-extended/placeholder-phpunit
A placeholder package for PHPUnit in php-extended projects. It provides minimal stubs so libraries can reference PHPUnit types without a hard dependency, keeping production installs lean while remaining compatible when PHPUnit is present.
What the package actually is. php-extended/placeholder-phpunit is a Composer-installable stub of PHPUnit's public API surface (interfaces, and likely the abstract classes such as PHPUnit\Framework\TestCase and Constraint despite the "interfaces only" description). Its purpose is to let a library declare a runtime type dependency on PHPUnit symbols without pulling phpunit/phpunit and its dependency tree into production installs. It ships no test runner, no assertions implementation, and no behaviour — only type declarations.
Architecture fit. Low for a Laravel application, narrow-but-legitimate for a distributed Laravel/PHP library. The only scenario where this earns its place is a package whose public API exposes PHPUnit types — custom constraints, custom assertions, base test-case classes, data-provider helpers, PHPUnit extensions — and which does not want to force every consumer to install PHPUnit. In a typical Laravel app this is a non-problem: PHPUnit already lives in require-dev, and nothing in app/ should reference PHPUnit\* at all. A TPM should treat this as a library-authoring tool, not an application dependency.
Integration feasibility. Mechanically trivial — one composer require line, no service provider, no config, no runtime footprint. The feasibility risk is almost entirely about provenance and version alignment, not effort. The repository is a GitLab-hosted, single-maintainer project inside the sprawling php-extended namespace, with zero stars and zero dependents. That is effectively zero production evidence: no downstream usage to learn failure modes from, no issue history to read, no compatibility matrix.
Technical risk.
TestListener removed in PHPUnit 10, TestCase signatures and expectException* behaviour evolved, metadata moved from annotations to attributes, Constraint internals shifted. A placeholder must be re-cut for every PHPUnit major, and a stale stub produces TypeError/fatal signature-mismatch errors rather than clean failures — frequently in consumer projects, not in this repo's CI. Laravel 11/12 sit on PHPUnit 10/11/12; a placeholder pinned to PHPUnit 7/8-era interfaces is worse than useless.phpunit/phpunit are present, both mapping the same fully-qualified class names. Composer's PSR-4 resolution picks whichever directory it searches first; nothing declares a conflict. The result usually "works" but makes the effective definition of TestCase/Constraint depend on autoload ordering — a genuinely nasty, non-obvious debugging class.instanceof and static analysis but will fail any code that calls a concrete method on those types, or that mocks/extends them expecting real behaviour. Teams routinely misread "interfaces exist" as "PHPUnit works."require (production) rather than require-dev enlarges the blast radius for a package that adds no product value.Key questions to resolve before any adoption.
conflict with phpunit/phpunit, or is coexistence undefined?Stack fit. Poor-to-conditional. Laravel's own conventions push PHPUnit to require-dev, and Laravel's testing story is now dual-track (PHPUnit or Pest), which makes hard-coding PHPUnit type contracts in a shipped package less attractive than it was. If a package must expose PHPUnit extension points, the idiomatic alternatives are: keep phpunit/phpunit in require-dev and accept that extension points are only exercised in test contexts; or define your own thin, version-stable interfaces and adapt to PHPUnit internally. Both avoid the stub entirely.
Migration path (if adopted at all). Restrict it to the require section of a single library — never to an application skeleton. Keep phpunit/phpunit in that library's require-dev and in the consuming app's require-dev. Add an explicit conflict or a CI assertion that only one provider of PHPUnit\ symbols is on the autoload path. Pin the placeholder to an exact version, not a range, and treat PHPUnit major upgrades as a paired-upgrade event.
Compatibility. Validate against the PHPUnit major actually in use across the estate (10/11/12 for current Laravel), not against a single app. Check PHP version constraints against Laravel's minimum (8.2+ for Laravel 11/12). Confirm Pest coexistence — Pest layers on PHPUnit, so a stub that shadows PHPUnit classes is especially hazardous in Pest projects.
Sequencing. (1) Audit internal packages for PHPUnit symbols in public signatures — likely a short list or empty. (2) Verify Packagist availability, tags, and PHPUnit-version alignment. (3) Timebox a half-day spike: install placeholder + real PHPUnit in one sandbox, dump get_class/ReflectionClass::getFileName() for TestCase and Constraint to see which provider wins, and run the suite. (4) If it passes, canary in one low-traffic library release. (5) Otherwise, document the decision as "rejected — use require-dev" so it is not re-litigated.
Recommendation. Do not adopt. With zero stars, zero dependents, a score of 0, and an unverified maintenance cadence against a moving PHPUnit API, the package's expected benefit (avoiding a dev dependency in production) is smaller than its tail risk (opaque autoload collisions and signature drift surfacing in downstream consumers). The opportunity score of 36.3 is consistent with a niche idea that is real but already better served by standard Composer practice.
Maintenance. Effectively self-maintenance. The placeholder has no community to absorb PHPUnit's churn, so your team inherits the obligation to fork or patch it on every PHPUnit major (roughly annual) and on any signature change in between. Budget a recurring few hours per PHPUnit upgrade plus unplanned triage whenever a consumer reports a class-shape error.
Support. No vendor, no SLA, no security policy, single maintainer on GitLab. Internal support will fall to whoever added the dependency; that person becomes the de facto owner. Incidents will arrive as confusing downstream bug reports rather than as alerts on this package.
Scaling. Runtime cost is negligible — interface declarations only, no execution path, no memory or latency contribution. The scaling cost is organisational: each additional PHPUnit major in the supported matrix multiplies release-management work, and the risk surface grows with every package that adopts it, because autoload-ordering behaviour is global and hard to reason about per-package.
Failure modes to instrument for.
TypeError or "declaration must be compatible" errors in a consumer's test suite after a routine composer update.--no-dev production install.Ramp-up. Conceptually cheap to learn — it is a Composer autoloading and PHPUnit-extension-points problem, not a framework problem — but correctly reasoning about it requires someone comfortable with PSR-4 resolution order, composer why, and PHPUnit's versioned API. Estimate half a day to evaluate, one engineer-day to fork-and-own if pursued, and an ongoing cognitive tax on every PHPUnit upgrade. For a package that solves one narrow packaging problem, that is a poor ratio; the honest recommendation is to decline and standardise on require-dev PHPUnit plus, where genuinely needed, project-owned interfaces.
How can I help you explore Laravel packages today?