tienvx/pact-provider-bundle
Pact provider bundle for PHP that helps verify your API against consumer-driven contracts. It streamlines provider states, verification endpoints, and test workflows so teams can catch breaking changes early and ship compatible services with confidence.
tienvx/pact-provider-bundle is a Symfony bundle for Pact provider-side verification. In a Laravel codebase you will not get the usual package auto-discovery experience — the bundle registers a console command and a PHPUnit listener through Symfony's DI container, so on Laravel you either (a) run it inside a thin Symfony kernel you boot only for contract tests, or (b) treat it as a reference implementation and drive pact-php's ProviderVerifier yourself from an artisan command. Pick (a) if you already have a Symfony service definition lying around, (b) if you want zero extra framework surface in composer.json.
Minimal steps:
composer require --dev tienvx/pact-provider-bundle — keep it in require-dev, it has no business in production.pact_provider.yaml in Symfony; on Laravel mirror the same keys into config/pact.php and read them in your own command).PACT_BROKER_BASE_URL, PACT_BROKER_TOKEN) or a local pacts/ directory of consumer JSON files.Look first at: the bundle's VerifyCommand (it is the whole product surface), its ProviderVerifier wrapper, and the PHPUnit listener class. Everything else is config plumbing.
Local files first, broker second. Keep a pacts/ directory checked in (or downloaded in a CI step) and run verification against it while developing. Only add broker publishing/pulling once the local loop is green — broker failures (auth, version selectors, pending pacts) will otherwise mask real contract mismatches.
Provider states are the integration seam. The bundle's state handler receives a state name plus params from the consumer and must put your Laravel app into that state. Standard pattern: a PactStateHandler class with a match on state name that calls Eloquent factories/seeders, wrapped in DB::beginTransaction() / rollBack() so each state is isolated. Keep state names boring and stable — they are a public contract with every consumer team.
Boot the app once, not per request. The verifier will hit your HTTP endpoint N times. Use php artisan serve in a background process (or better, a pre-warmed Octane/php -S against public/index.php) for the duration of the run, and have the state handler talk to the same database the server uses. A common setup is a dedicated pact connection and a --env=pact boot so you never touch dev data.
CI shape. One job per consumer contract, or one job with a matrix over pact files, so a single failing consumer doesn't hide the others. Run it after your unit/feature suites — contract verification is slow (real HTTP + DB seeding) and you want fast feedback first. Publish results back to the broker on the main branch only, with the provider version set to the commit SHA.
Tagging and deployment gates. Use broker tags (main, production) with can-i-deploy in a pre-deploy step. This is where the package earns its keep: the provider build fails before deploy if a consumer's expectations are unmet, rather than at 2am.
Wrapper command. Regardless of entry point, expose a single php artisan pact:verify (or a composer pact script) so CI config, onboarding docs, and local dev all invoke the same thing. Put the bundle-specific flags in one place; nobody should be memorising verifier options.
Combine with consumer verification using pact-php's consumer side in your own JS/PHP clients' test suites, and treat the broker as the source of truth rather than passing pact files around between repos.
This is a Symfony bundle. There is no Laravel service provider. composer require alone will not register the command, and vendor/bin/… won't find it. Plan for the adapter work explicitly; if your team won't maintain it, use pact-php directly — the bundle is thin.
The verifier calls over the network. Forgetting to start the server (or starting it on a port that differs from PACT_PROVIDER_BASE_URL) produces connection-refused errors that look like contract failures. Assert server health with a curl step before verification.
Env var precedence is a trap. Broker URL/token, provider base URL, provider version, and consumer version selectors can come from config files, env vars, or CLI flags. When something behaves unexpectedly, dump the fully resolved config — the flags on the command line usually win, and CI often sets an env var that silently overrides your YAML.
State handlers are not sandboxed. If a state handler throws, the verifier reports a generic verification failure. Log loudly inside the handler and rethrow with the state name in the message. Transactional rollback does not cover Redis, queues, or filesystem state — clean those up explicitly.
Time and randomness break contracts. Use Carbon::setTestNow() and fixed seeds in state handlers; otherwise a date-formatting mismatch fails intermittently against an otherwise-correct contract.
Don't assert on your own response in provider tests. The provider's job is to satisfy the consumer's matchers, not to re-specify its own behaviour. Duplicating expectations here means every provider change requires double edits — and it's how teams conclude "contract testing is too much work."
Broker publishing is easy to get wrong. Publish consumer versions from the consumer pipeline, provider verification results from the provider pipeline, and never publish from a developer laptop — junk versions pollute the matrix and make can-i-deploy untrustworthy.
Debugging: run with verbose output and inspect the individual request/response diff, not just the summary. Most "mismatch" reports are one extra/missing field or a type drift (int vs string) that a strict JSON comparison catches immediately.
Extension points are limited by design: state handlers, a custom verifier/output formatter, and the config surface. If you need more than that (custom matchers, request mutations), you're outside this package and into pact-php internals — accept that and pin the version.
On this release (v1.0.4): changes are test renames, a doc return-type fix, and an internal refactor removing a condition. No API, config, or command-line changes — no action required beyond the normal patch bump, and no reason to change the patterns above.
How can I help you explore Laravel packages today?