Skip to content

Commit fe1d5e1

Browse files
committed
docs: add documentation landing hub and getting-started guide
Kick off in-repo documentation under docs/ (issue #486). Adds the landing hub, the getting-started walkthrough (including why turnOn() must run before any curl/soap code is loaded), and a requirements page with the tested HTTP library matrix.
1 parent 66e985d commit fe1d5e1

3 files changed

Lines changed: 168 additions & 0 deletions

File tree

docs/getting-started.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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)

docs/index.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# php-vcr Documentation
2+
3+
You're in the docs. Full pitch, badges and a 30-second example live in the [README](../README.md) — this page
4+
is just the map.
5+
6+
| | |
7+
|---|---|
8+
| 📦 **[Getting Started](getting-started.md)** | Install, turn VCR on, record your first cassette. |
9+
| 🧭 **[Requirements](requirements.md)** | PHP/extension support matrix, tested HTTP libraries. |
10+
| 🔍 **[How VCR works](guides/how-vcr-works.md)** | The two interception mechanisms — and why bootstrap order matters. |
11+
| 🎞️ **[Cassettes](guides/cassettes.md)** | What gets recorded, the file format, identical-request sequencing. |
12+
| 🎬 **[Record Modes](guides/record-modes.md)** | `new_episodes` / `once` / `none` / `all` — and when to use which. |
13+
| 🎯 **[Request Matching](guides/request-matching.md)** | How php-vcr decides a request "matches" a recording. |
14+
| 🧑‍🍳 **How-to** | [PHPUnit](howto/use-with-phpunit.md) · [Codeception](howto/use-with-codeception.md) · [Filter sensitive data](howto/filter-sensitive-data.md) · [Custom matcher](howto/custom-request-matcher.md) · [Select hooks](howto/select-library-hooks.md) · [SOAP](howto/record-soap.md) |
15+
| 📖 **Reference** | [VCR facade](reference/vcr-facade.md) · [Configuration](reference/configuration.md) · [Request matchers](reference/request-matchers.md) · [Library hooks](reference/library-hooks.md) · [Storage backends](reference/storage-backends.md) · [Events](reference/events.md) · [Request/Response](reference/request-response.md) |
16+
| 🆙 **[Upgrading](upgrading.md)** | Breaking changes by version. |
17+
18+
Contributing to php-vcr, including how to write documentation? See [CONTRIBUTING.md](../CONTRIBUTING.md).

docs/requirements.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Requirements
2+
3+
> One-liner: PHP 8.0–8.5 (minus the broken 8.2.0–8.2.8 range), `ext-curl` always, `ext-soap`/`ext-xml` only if
4+
> you use the SOAP hook.
5+
6+
**On this page:** [PHP](#php) · [Extensions](#extensions) · [Dependencies](#dependencies) · [Tested HTTP libraries](#tested-http-libraries)
7+
8+
## PHP
9+
10+
```
11+
^8.0,<8.2 | >=8.2.9,<8.6
12+
```
13+
14+
That is: PHP 8.0, 8.1, 8.3, 8.4, 8.5 — and 8.2 **only from patch 8.2.9 onward** (earlier 8.2.x patches have a
15+
known incompatibility). CI runs the full matrix across every supported version, on both lowest and highest
16+
allowed dependency versions.
17+
18+
## Extensions
19+
20+
| Extension | Required | Why |
21+
|---|---|---|
22+
| `curl` | Always | Used both by the `curl` hook and internally to perform real HTTP requests when recording. |
23+
| `soap` | Only for the `soap` hook | `SoapHook` throws `BadMethodCallException` at construction if `SoapClient` doesn't exist. |
24+
| `xml` (`ext-dom`) | Only for the `soap` hook | Same constructor check, via `DOMDocument`. |
25+
26+
If you never use `SoapClient` in your codebase, you can skip `ext-soap`/`ext-xml` entirely — the `soap` hook
27+
is simply never constructed unless something asks for it.
28+
29+
## Dependencies
30+
31+
Composer installs all of these for you:
32+
33+
- [`symfony/event-dispatcher`](https://github.com/symfony/event-dispatcher) — powers [Events](reference/events.md).
34+
- [`symfony/yaml`](https://github.com/symfony/yaml) — the default [storage backend](reference/storage-backends.md).
35+
- [`beberlei/assert`](https://github.com/beberlei/assert) — internal input validation.
36+
37+
## Tested HTTP libraries
38+
39+
php-vcr intercepts at the level of PHP's stream wrapper and `curl_*`/`SoapClient` functions, so it works with
40+
any library built on top of them — these are the ones actually exercised by the test suite:
41+
42+
| Library | Hook | Notes |
43+
|---|---|---|
44+
| `file_get_contents()`, `fopen()`, `fread()`, … | `stream_wrapper` | Any function that goes through PHP's `http`/`https` stream wrapper. |
45+
| `Symfony\Component\HttpClient\NativeHttpClient` | `stream_wrapper` | Uses stream wrappers under the hood. |
46+
| `ext-curl` (`curl_init`/`curl_exec`/`curl_multi_*`/…) | `curl` | Direct curl function calls. |
47+
| `Symfony\Component\HttpClient\CurlHttpClient` | `curl` | Symfony's curl-backed client. |
48+
| Guzzle (curl handler) | `curl` | Guzzle's default transport on most installs. |
49+
| `SoapClient` (built-in or a subclass) | `soap` | Both direct instantiation and custom `extends SoapClient` classes. |
50+
51+
See [Library Hooks](reference/library-hooks.md) for how each hook actually intercepts these, and
52+
[How VCR works](guides/how-vcr-works.md) for the underlying mechanism.
53+
54+
---
55+
[Documentation home](index.md) · Next: [Getting Started](getting-started.md)

0 commit comments

Comments
 (0)