|
| 1 | +# Getting Started |
| 2 | + |
| 3 | +> One-liner: install php-vcr, turn it on as early as possible, insert a cassette, make requests — first run |
| 4 | +> records, every run after that replays. |
| 5 | +
|
| 6 | +**On this page:** [Requirements](#requirements) · [Install](#install) · [Turn it on — early](#turn-it-on-early) · [Record, then replay](#record-then-replay) · [Next steps](#next-steps) |
| 7 | + |
| 8 | +## Requirements |
| 9 | + |
| 10 | +The short version: PHP 8, `ext-curl`. Full compatibility matrix (including which HTTP libraries are covered |
| 11 | +and when `ext-soap`/`ext-xml` matter) lives in [Requirements](requirements.md). |
| 12 | + |
| 13 | +## Install |
| 14 | + |
| 15 | +```bash |
| 16 | +composer require --dev php-vcr/php-vcr |
| 17 | +``` |
| 18 | + |
| 19 | +## Turn it on — early |
| 20 | + |
| 21 | +> **⚠️ Warning — this is the part everyone gets wrong once.** `VCR::turnOn()` must run **before** any file that |
| 22 | +> calls `curl_*` or instantiates `SoapClient` is loaded — ideally right after Composer's autoloader, in your |
| 23 | +> test bootstrap. The `curl` and `soap` hooks work by rewriting source code as PHP `include`s/`require`s it; |
| 24 | +> code that's already loaded when `turnOn()` runs cannot be rewritten anymore. See |
| 25 | +> [How VCR works](guides/how-vcr-works.md) for the full mechanism — it also explains a sharp edge: those two |
| 26 | +> hooks only ever rewrite code loaded via `include`/`require`, **not** the top-level script PHP was invoked |
| 27 | +> with. In a real test suite this is a non-issue (PHPUnit loads your test classes via the autoloader), but a |
| 28 | +> raw script with `curl_exec()` written directly at the top level will silently bypass interception. |
| 29 | +
|
| 30 | +```php |
| 31 | +// tests/bootstrap.php |
| 32 | +require __DIR__ . '/../vendor/autoload.php'; |
| 33 | + |
| 34 | +\VCR\VCR::turnOn(); |
| 35 | +\VCR\VCR::turnOff(); |
| 36 | +``` |
| 37 | + |
| 38 | +That `turnOn()`/`turnOff()` pair looks pointless but isn't: `turnOn()` is what registers the `curl`/`soap` |
| 39 | +source rewriting — a one-time, permanent registration for the rest of the process. `turnOff()` right after |
| 40 | +just flips the hooks back to passthrough; it doesn't undo the registration. So this pattern gets the |
| 41 | +registration done as early as possible **without** leaving hooks live for your whole test run. Each individual |
| 42 | +test then calls `turnOn()` again — cheaply, since the registration already happened — only when it actually |
| 43 | +wants a cassette, and `turnOff()` when it's done (see the example below). |
| 44 | + |
| 45 | +The `stream_wrapper` hook (used by `fopen()`, `file_get_contents()`, …) doesn't have this restriction — it |
| 46 | +replaces the `http`/`https` stream wrapper globally, so it works no matter where the call is written. |
| 47 | + |
| 48 | +## Record, then replay |
| 49 | + |
| 50 | +```php |
| 51 | +use PHPUnit\Framework\TestCase; |
| 52 | + |
| 53 | +class ExampleTest extends TestCase |
| 54 | +{ |
| 55 | + public function testFetchesExampleDotCom(): void |
| 56 | + { |
| 57 | + \VCR\VCR::turnOn(); |
| 58 | + \VCR\VCR::insertCassette('example'); |
| 59 | + |
| 60 | + // First test run: no recording exists yet -> a real HTTP request is made and recorded. |
| 61 | + // Every run after that: the cassette has a match -> the real request is never sent. |
| 62 | + $result = file_get_contents('http://example.com'); |
| 63 | + |
| 64 | + $this->assertNotEmpty($result); |
| 65 | + |
| 66 | + \VCR\VCR::eject(); |
| 67 | + \VCR\VCR::turnOff(); |
| 68 | + } |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +> **💡 Tip:** if your request contains something that changes on every call — a timestamp, a nonce, a |
| 73 | +> generated idempotency key — the default configuration (all matchers enabled) will never replay, since the |
| 74 | +> exact body/query string never matches again. Narrow the enabled matchers to ignore that part, e.g. |
| 75 | +> `VCR::configure()->enableRequestMatchers(['method', 'url', 'host']);`. See |
| 76 | +> [Request Matching](guides/request-matching.md). |
| 77 | +
|
| 78 | +Cassettes land in the configured cassette path (default `tests/fixtures`, see |
| 79 | +[Configuration](reference/configuration.md#cassette-path)) as a file named exactly `example` — php-vcr does |
| 80 | +**not** append `.yml`/`.json` automatically. If the cassette name contains a path separator |
| 81 | +(`'api/example'`), the subfolder is created for you. |
| 82 | + |
| 83 | +Delete the cassette file and re-run the test to force a fresh recording — that's the entire "re-record" |
| 84 | +workflow for `new_episodes` (the default mode). For other strategies, see |
| 85 | +[Record Modes](guides/record-modes.md). |
| 86 | + |
| 87 | +## Next steps |
| 88 | + |
| 89 | +- [How VCR works](guides/how-vcr-works.md) — the two interception mechanisms, and why bootstrap order matters. |
| 90 | +- [Record Modes](guides/record-modes.md) — `new_episodes` / `once` / `none` / `all`. |
| 91 | +- [Request Matching](guides/request-matching.md) — how php-vcr decides a request "matches" a recording. |
| 92 | +- [Use with PHPUnit](howto/use-with-phpunit.md) — the manual lifecycle, wired into a real test class. |
| 93 | + |
| 94 | +--- |
| 95 | +[Documentation home](index.md) · Next: [How VCR works](guides/how-vcr-works.md) → |
0 commit comments