knplabs/phpspec-welldone-extension
A PhpSpec extension that adds a bit of encouragement to your test runs: when the whole suite passes, it prints a congratulatory "well done" message instead of leaving you with plain green output. Small, drop-in composer package that hooks into PhpSpec's event system to celebrate successful...
Read this first: the package is archived, unmaintained, and has essentially zero adoption (6 stars, 0 dependents). That does not make it dangerous — it is a tiny dev-only PhpSpec extension — but it means you are your own support line the moment any PHP or PhpSpec bump breaks it. Budget an afternoon to read the source before you commit to it on a long-lived branch.
Note the description mismatch. The package's own description.md describes a narrow feature: print a celebratory Well done! line after a green PhpSpec run. Some of the surrounding assessments describe it as a spec-gap reporter ("which classes are unspecified?"). Verify which behavior the version you install actually delivers by reading src/ — do not trust the marketing copy from any source.
Minimal install:
composer require --dev knplabs/phpspec-welldone-extension
Register the extension in phpspec.yml (or phpspec.yml.dist):
extensions:
- Knp\PhpSpec\WellDone\Extension
First run:
vendor/bin/phpspec run
If the suite is green you should see the celebratory output appended. If it is not green, output should be unchanged from a normal PhpSpec run.
Where to look in the source:
composer.json — the require block tells you which PhpSpec major it was last tested against; check this against what you have installed before running.First real use case: you already have a PhpSpec suite that is green and want a small morale boost at the end of a local or CI run. That is the entire value proposition. It has no bearing on coverage, quality, or spec completeness unless the version you install also does the gap scan — in which case see Implementation Patterns below.
Immediate sanity check: if you are running PHPUnit, Pest, Behat, or anything other than PhpSpec, stop. There is nothing for you here. Standing up PhpSpec purely to run this extension inverts the cost/benefit by an order of magnitude.
Local-dev celebration only (the safe default). Wire the extension into your local phpspec.yml but exclude it from your committed config so CI output stays deterministic and diffable:
# phpspec.yml.local — gitignored
extensions:
- Knp\PhpSpec\WellDone\Extension
This is the least surprising integration: teammates who want the message opt in, and nothing in CI depends on the string output.
Fork-and-own pattern (recommended if you rely on it at all). Because the repo is archived, do not add it to composer.json as a versioned dependency you expect to update. Instead:
# One-time, into an internal tools namespace
git clone https://github.com/KnpLabs/PhpSpecWellDoneExtension /tmp/welldone
cp -R /tmp/welldone/src/* tools/phpspec-welldone/src/
Then register your vendored class in phpspec.yml. This means PHP 8.x compatibility fixes are a one-line patch instead of a fork of the upstream repo, and dependency scanning does not flag you for an unsupported package.
Reimplementation pattern (if you actually need the spec-gap scan). If your interest is really "which classes have no spec?", do not buy it from this package. It is roughly a couple hundred lines of work:
// tools/spec-gap.php — run with `php tools/spec-gap.php`
$srcRoot = __DIR__ . '/../src';
$specRoot = __DIR__ . '/../spec';
$iter = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($srcRoot));
foreach ($iter as $file) {
if ($file->getExtension() !== 'php') continue;
$rel = str_replace([$srcRoot . '/', '.php'], ['', 'Spec.php'], $file->getPathname());
if (!file_exists($specRoot . '/' . $rel)) {
echo $file->getPathname() . PHP_EOL;
}
}
That gives you the same signal with an owner, a support path, and — critically — the ability to target PHPUnit/Pest instead of locking into PhpSpec if your team is migrating.
CI integration pattern (non-blocking first). If you must run it in CI, run it as an informational step for two sprints before considering making it blocking:
- run: vendor/bin/phpspec run
continue-on-error: false # the suite itself must pass
- run: php tools/spec-gap.php > spec-gap.txt || true
continue-on-error: true
Commit spec-gap.txt as an artifact so someone can triage the delta across sprints. The real cost of this pattern is not the tool; it is the recurring human attention required to triage the report.
Composition with a scheduled job. If you are using the gap scan as a legacy-audit instrument, run it weekly rather than per-commit:
on:
schedule: [{cron: '0 9 * * MON'}]
This produces a defensible trend line without the noise of per-PR churn.
Version compatibility is the primary failure mode. The repo is archived; its last tagged release predates modern PHP 8.x and the current symfony/console/symfony/finder versions PhpSpec depends on. Before you add it, check what phpspec/phpspec version you have and what this extension's composer.json was built against. Pin to a specific commit or tag — never dev-master, and never rely on a floating constraint that could re-resolve to a broken transitive dependency.
The quiet-failure trap. If the extension does more than print a message (i.e., performs a gap scan), misconfigured source paths or naming-convention drift can make it report "all clear" when it should be reporting gaps. This is worse than a crash because it produces false confidence in coverage. Mitigate with a known-gap fixture — a deliberately unspecified class in a test namespace that the scan must always flag — and run it on every CI invocation:
// tests/fixtures/src/AlwaysUnspecified.php — no spec file exists, ever
namespace Tests\Fixtures;
class AlwaysUnspecified {}
If the report ever stops listing this class, your configuration is broken, not your codebase.
Case-sensitivity on CI. PSR-4 root discovery is filename-based. A spec file named FooSpec.php on a case-insensitive macOS filesystem maps cleanly; the same file checked out on Linux CI may not, depending on how the tool normalizes paths. Test the scan locally and on CI before you trust the output.
Interfaces, traits, enums, and generated code produce false positives. Filename-based spec inference has no way to know that App\Contracts\FooInterface has no spec by design. Curate an allowlist of directories or suffixes to exclude, and treat the first run as a survey rather than as a number to optimize.
quiet mode suppresses the message. Running phpspec run --quiet or -q (or whatever your PhpSpec version uses) will typically hide any custom extension output, including the celebration string. Not a bug, but do not expect the message in minimal-output CI logs.
Do not gate CI on the celebration string. If any pipeline does grep -q "Well done" against the PhpSpec output, a PhpSpec patch-level upgrade that changes formatter behavior will silently break your build. If you must assert it, assert exit codes, not strings.
Extension points. If you are forking, the natural extension points are: the formatter class (to change the message or output channel), the source/spec root configuration (to handle monorepos with multiple PSR-4 roots), and the filename mapping strategy (to handle naming-convention drift). Do not try to extend it for anything beyond that — the surface area is a few hundred lines and you will outgrow it faster than you can patch it.
When to just delete it. If the team is migrating to PHPUnit/Pest, if PhpSpec is on its way out, or if the message is the only value you are extracting, remove the dependency rather than carrying an unmaintained dev package through your next toolchain upgrade. An archived dependency with zero dependents will surface in your next SCA scan as a conversation you have to have — and the conversation will end with "remove it."
How can I help you explore Laravel packages today?