|
| 1 | +# Laravel Mailtrap Driver — Agent Guide |
| 2 | + |
| 3 | +## Commands |
| 4 | + |
| 5 | +```bash |
| 6 | +composer run check # lint → analyse → test (preferred) |
| 7 | +composer run test # PHPUnit with Clover + HTML coverage → coverage/ |
| 8 | +composer run analyse # PHPStan level 8, --memory-limit=1G |
| 9 | +composer run format # Pint — modifies files in-place |
| 10 | +composer run lint # Pint --test — checks formatting without modifying |
| 11 | +``` |
| 12 | + |
| 13 | +**Coverage gotcha:** `composer run test` and `composer run check` both require a coverage driver (Xdebug or pcov). Without one, PHPUnit exits with error. For local runs: |
| 14 | +```bash |
| 15 | +vendor/bin/phpunit --no-coverage |
| 16 | +``` |
| 17 | + |
| 18 | +## Architecture |
| 19 | + |
| 20 | +Four source files in `src/`, all under namespace `Ronald2Wing\LaravelMailtrap\`: |
| 21 | + |
| 22 | +| File | Role | |
| 23 | +|------|------| |
| 24 | +| `MailtrapServiceProvider.php` | Registers the `mailtrap` transport. Reads from `services.mailtrap` by default; each mailer may override via `'config' => 'services.mailtrap.marketing'`. | |
| 25 | +| `MailtrapTransport.php` | Symfony `AbstractTransport`. POSTs to `apiUrl()` with JSON from `PayloadBuilder`. | |
| 26 | +| `PayloadBuilder.php` | Translates Symfony `Email` + `Envelope` into Mailtrap's JSON payload. Pure, no I/O. | |
| 27 | +| `MailtrapConfig.php` | Readonly value object — token, endpoint, optional inbox id, Guzzle options. Public constants: `SEND_ENDPOINT`, `BULK_ENDPOINT`, `SANDBOX_ENDPOINT`. Factory: `fromArray()`. | |
| 28 | + |
| 29 | +### MailtrapConfig config mapping (`fromArray`) |
| 30 | + |
| 31 | +| Config key (`services.mailtrap.*`) | Property | Notes | |
| 32 | +|------------------------------------|----------|-------| |
| 33 | +| `token` | `apiToken` | Required; must be non-empty | |
| 34 | +| `endpoint` | `endpoint` | Defaults to `SEND_ENDPOINT` | |
| 35 | +| `inbox_id` | `inboxId` | Required when endpoint is `SANDBOX_ENDPOINT` | |
| 36 | +| `http` | `httpOverrides` | Guzzle options; merged over `DEFAULT_HTTP_OPTIONS` (`connect_timeout: 10`, `timeout: 30`) | |
| 37 | + |
| 38 | +### PayloadBuilder header handling |
| 39 | + |
| 40 | +Two constants control Mailtrap-specific headers: |
| 41 | + |
| 42 | +| Constant | Headers mapped | Value handling | |
| 43 | +|----------|---------------|----------------| |
| 44 | +| `MAILTRAP_STRING_HEADERS` | `X-Mailtrap-Category` → `category`, `X-Mailtrap-Template-Uuid` → `template_uuid` | Passed as-is (string) | |
| 45 | +| `MAILTRAP_JSON_HEADERS` | `X-Mailtrap-Template-Variables` → `template_variables`, `X-Mailtrap-Custom-Variables` → `custom_variables` | JSON-decoded to array | |
| 46 | + |
| 47 | +Standard headers are defined in `RFC5322_HEADERS`. Both header constants feed `reservedHeaderNames()`, used by `extractForwardedHeaders()` to filter reserved headers out of the forwarded `headers` block. `extractReplyTo()` handles Reply-To separately. Both are merged in `build()`. |
| 48 | + |
| 49 | +### Adding a new Mailtrap-specific header |
| 50 | + |
| 51 | +1. Add an entry to `PayloadBuilder::MAILTRAP_STRING_HEADERS` or `PayloadBuilder::MAILTRAP_JSON_HEADERS` depending on whether the value needs JSON decoding. |
| 52 | +2. Done. `extractMailtrapHeaders` picks it up, `reservedHeaderNames` strips it from the forwarded headers block. |
| 53 | + |
| 54 | +## Static Analysis |
| 55 | + |
| 56 | +PHPStan runs at **level 8** with **no ignored errors** on both `src/` and `tests/`. Never add baseline entries or `@phpstan-ignore` comments. |
| 57 | + |
| 58 | +## CI |
| 59 | + |
| 60 | +CI (`php.yml`) runs **test → lint → analyse** (different order from `composer check` which is lint → analyse → test). It also runs `composer validate --strict`. |
| 61 | + |
| 62 | +## Testing |
| 63 | + |
| 64 | +- **Base classes**: `Orchestra\Testbench\TestCase` for container-aware tests; `PHPUnit\Framework\TestCase` for pure unit tests. |
| 65 | +- **HTTP mocking**: Guzzle `MockHandler` + `HandlerStack` with a history middleware. |
| 66 | +- **Reflection**: `extractMessageId` (private static) is tested via `invokeProtected` helper in `MailtrapTransportTest`. |
| 67 | +- `getEnvironmentSetUp()` configures `services.mailtrap`, `mail.default`, and `mail.from` for integration tests. |
| 68 | + |
| 69 | +## Conventions |
| 70 | + |
| 71 | +- `declare(strict_types=1)` in every file. |
| 72 | +- `composer.json` has `sort-packages: true` — Composer auto-sorts after `composer require`. Keep it valid for CI. |
| 73 | +- PHPDoc with `@param` array shapes on every public/protected method. |
0 commit comments