Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Vcr Bundle Laravel Package

php-vcr/vcr-bundle

Symfony integration for PHP-VCR that records real HTTP interactions and replays them in your test suite. Configure cassettes, hooks and fixtures to keep functional tests fast, deterministic and offline without changing your HTTP client code.

View on GitHub
Deep Wiki
Context7

Getting Started

Install (dev-only):

composer require --dev php-vcr/vcr-bundle:^3.0

This pulls in php-vcr/vcr (^1.6) plus a Symfony bundle (VCRVCRBundle) and a web-profiler data collector. Register it only for dev/test — there is no need for it in prod, and you do not want cassette paths or curl hooks leaking into production boot:

// config/bundles.php
return [
    // ...
    VCR\VCRBundle\VCRVCRBundle::class => ['dev' => true, 'test' => true],
];

Requirements as of 3.0 (up from 2.x): PHP ^8.1 (CI covers 8.3/8.4, with 8.5 behind continue-on-error until GA), Symfony ^5.4|^6.4, PHPUnit ^9.6|^10.5 (dev). If you are on PHP 7.x or Symfony 4, stay on the 2.x line.

Where to look first, in order:

  1. DependencyInjection/Configuration.php in the bundle — this is the authoritative config tree (cassette.path, storage, mode, whitelist, etc.). The README is thin; the config tree is short and readable.
  2. Test/VCRTestCaseTrait.php (or wherever VCRTestCaseTrait lives in your tag) — new to 3.0's test tooling; it wraps insert/eject around each test. Read it before hand-rolling your own base case, because it already solves the cassette-leak problem described below.
  3. The php-vcr/vcr README — the bundle is a thin Symfony wrapper, so all the real concepts (cassettes, modes, request matchers, library hooks) live upstream.
  4. DataCollector/ — tells you exactly what the profiler panel exposes, which is your main debugging tool.

Minimal config (verify key names against Configuration.php for your installed version):

# config/packages/test/vcr.yaml
vcr:
    enabled: true
    cassette:
        path: '%kernel.project_dir%/tests/Fixtures/cassettes'
    storage: json          # or yaml
    mode: once             # once | new_episodes | none | all

First use case — record a real HTTP call once, then replay it forever:

use VCR\VCR;

public function testGithubApi(): void
{
    VCR::insertCassette('github_user_octocat');

    $response = (new \GuzzleHttp\Client())->get('https://api.github.com/users/octocat');

    $this->assertSame(200, $response->getStatusCode());
    $this->assertSame('octocat', json_decode($response->getBody(), true)['login']);

    VCR::eject();
}

Run it once with network access, and a cassette file appears under tests/Fixtures/cassettes/. Delete the file and re-run to re-record. That record/replay loop is the package.

Implementation Patterns

Prefer the bundled VCRTestCaseTrait over a hand-rolled base case. 3.0 updated it for PHPUnit 10, where getName() was removed — it now uses name() / dataName() with a backwards-compatible fallback for PHPUnit 9.6. That means one trait covers both supported PHPUnit majors, and cassette names follow the data-provider-aware test name rather than just the method:

use VCR\VCRBundle\Test\VCRTestCaseTrait;

final class GithubApiTest extends KernelTestCase
{
    use VCRTestCaseTrait;

    public function testOctocat(): void { /* cassette handled for you */ }
}

Check the trait source for its cassette path/name convention before adopting it — if it doesn't match your fixture layout, subclass or copy it rather than fighting it.

If you need custom naming, wrap insert/eject in a base TestCase. VCR is a static, process-global facade — it is not a Symfony service you inject. Almost every flaky-VCR bug comes from a cassette surviving into the next test:

abstract class VcrTestCase extends KernelTestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        VCR::insertCassette(str_replace('\\', '_', static::class) . '_' . $this->name());
    }

    protected function tearDown(): void
    {
        VCR::eject();
        parent::tearDown();
    }
}

Drive mode from the environment, not from code. The four modes map cleanly onto a workflow:

Mode Use
once Local dev default — record if the cassette is missing, replay otherwise
none CI — never touch the network; missing cassette = red build
new_episodes Add a new interaction to an existing cassette without wiping it
all Deliberate re-recording session, then commit the diff

Ship mode: none in config/packages/test/vcr.yaml and override to once locally via .env.test.local, or vice versa — pick one and be consistent.

Tighten request matching deliberately. The default matchers (method, host, path, query, body) compare a lot, so cassettes break on any header/query churn. For APIs where the URL is the identity, trim to what matters:

VCR::configure()->addRequestMatcher(
    'path_and_method',
    fn(Request $a, Request $b) => $a->getMethod() === $b->getMethod()
        && $a->getUrl() === $b->getUrl()
);

Add this in a bootstrap file wired through phpunit.xml (<php><file>bootstrap.php</file></php>), not inside individual tests — VCR::configure() is once-per-process.

Scrub credentials before the cassette hits disk. Auth headers, ?api_key=, session cookies and Date headers are what make cassettes unreviewable and leaky. Filter on the way in (a whitelist of hosts plus a RequestMatcher-adjacent normaliser in a bootstrap, or a thin custom Storage decorator) rather than post-editing JSON.

Use it for functional tests of real controllers. It pairs well with KernelBrowser: boot the kernel, make the request, and let CurlHook/StreamWrapperHook intercept the outbound call to a payment gateway or OAuth provider. The MockHttpClient covers your code; VCR covers the third-party protocol — they are different jobs and both are worth having.

Profiler workflow. In dev with framework.profiler on, the VCR panel shows the cassette name, recording mode, and each request/response. When a test fails with "no matching request", open the panel in a dev-environment reproduction — it's far faster than diffing the cassette JSON by hand.

Gotchas and Tips

  • 3.0 is a dependency-bump release, not a feature release. Bundle API, config keys and service names are unchanged, so the upgrade is composer require php-vcr/vcr-bundle:^3.0 and nothing else. Existing cassettes stay valid — no re-record required.
  • Symfony 7 is not supported in 3.0. Constraints are ^5.4|^6.4. If you're planning a Symfony 7 migration, VCR is one more thing that will hold you back, so plan the bundle upgrade separately from the framework upgrade.
  • Bump php-vcr/php-vcr to ^1.6 even if you pin the bundle yourself. 1.5.x validates the curl handle with is_resource(), which always returns false on PHP 8+ where curl_init() returns a CurlHandle object. Recording silently fails on modern PHP with 1.5 — this is the one dependency footgun the release explicitly closes.
  • Static state, not container state. The bundle applies vcr: config once at boot; every VCR::insertCassette() afterwards is a global side effect. Don't interleave cassettes in one test, and don't rely on the container to reset anything.
  • The bundle is a config bridge, not an API. There is no vcr service to inject. You call the VCR facade directly; the bundle's job is wiring VCR::configure() from YAML and registering the profiler collector.
  • ext-curl assumptions. Recording works because php-vcr hooks curl_exec (and the stream wrapper). Clients built on curl_multi — Guzzle's default handler, Symfony's CurlHttpClient under concurrency — can produce truncated or missing recordings. If cassettes come out empty, switch to a single-request curl handler or Symfony's NativeHttpClient before blaming the bundle.
  • No interception = empty profiler panel. An empty VCR panel is itself a diagnostic: your HTTP client isn't going through a hooked code path (e.g. a PSR-18 client implemented on sockets, or curl_multi). Check that before assuming the bundle is broken.
  • Cassettes and secrets. JSON cassettes happily record Authorization: Bearer … and Set-Cookie. Add a pre-commit check or scrub step; the package doesn't do it for you.
  • Commit churn. JSON storage diffs badly. For anything reviewed in PRs, prefer storage: yaml, and normalise volatile headers so re-records don't produce noise.
  • PHPUnit 10 upgrade path. The trait handles name()/dataName(), but any of your own helpers calling TestCase::getName() will break on PHPUnit 10 independently of VCR. If you pinned ^9.5 in composer.json, you can now widen to ^9.6|^10.5.
  • Version pinning still matters. This remains a low-traffic package (0 dependents, ~13 stars) whose releases track Symfony majors rather than shipping features. Read the composer.json constraints for the tag you install; last-release dates are not a proxy for maintenance. 3.0 is a deliberate LTS-alignment bump, so expect long gaps between releases rather than a stream of new knobs.
  • Extension points are upstream. Custom VCR\Storage\Storage implementations, extra request matchers, and the whitelist are all php-vcr/vcr concepts surfaced through bundle config. If a knob isn't in Configuration.php, drop to a PHP bootstrap and call VCR::configure() — but do it after kernel boot, or the bundle's boot-time configuration will overwrite yours.
  • Don't reach for it in unit tests. It's a process-wide HTTP interceptor; it costs boot time and makes test isolation harder. Reserve it for functional/integration tests where a real protocol shape (JSON:API pagination, OAuth redirect chains) is the thing under test.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator
manelgavalda/enrollment_mobile
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics