# Trace every Symfony order to its channel

> Add MIRA FIVE to a Symfony app with one bundle, the tracker tag in Twig and paid orders sent after the response, to see buyers and revenue per channel.

URL: https://mirafive.io/for/symfony  
Updated: 2026-09-28

One bundle prints the tracker in Twig and sends each paid order once the response has gone out.

Package `mirafive/sdk-symfony`

## Set up in 5 steps

1. ### Install the bundle  
With Symfony Flex the bundle registers itself. Without Flex, add `MiraFive\Symfony\MiraFiveBundle::class => ['all' => true]` to `config/bundles.php`. It needs PHP 8.3 or later and Symfony 6.4, 7 or 8.  
```  
composer require mirafive/sdk-symfony  
```
2. ### Add the two keys  
The secret key of a server source sends events from your app and never reaches a page. The website key of a website source goes into the tracker tag. Both are under **Data → Sources**. Keep them in `.env.local` or your secret store, never in the repository. No configuration file is needed.  
```  
MIRAFIVE_SECRET_KEY=mf_…  
MIRAFIVE_WEBSITE_KEY=mf_…  
```
3. ### Print the tracker in Twig  
`mirafive_script()` prints the two-line tracker with your website key, and nothing without one. It counts every page and starts in consentless mode, with no cookies and nothing stored on the device.  
```  
<head>  
    {{ mirafive_script() }}  
</head>  
```
4. ### Send the paid order  
`MiraFive\Mira` is autowired, so inject it where your app marks the order as paid. `track()` only buffers, and the bundle sends the buffer on `kernel.terminate`, after the response has reached the browser. You never call `flush()`.  
```  
use MiraFive\Mira;  
// $mira is the autowired MiraFive\Mira, e.g. a controller argument  
$mira->track('order_paid', userId: (string) $order->getCustomerId(), properties: [  
    'revenue' => 328,  
    'currency' => 'EUR',  
]);  
```
5. ### Check the setup  
The check command sends an install check, which is never stored or billed, and ends with "The secret key and host work. Nothing was stored or billed." Then open a page on its real domain and watch **Data → Live**.  
```  
bin/console mirafive:check  
```

MIRA FIVE is analytics built around the person. The Symfony bundle wires the PHP SDK into your container: `MiraFive\Mira` is autowired, a Twig function prints the tracker, and events leave on `kernel.terminate` and `console.terminate`, so no page waits for analytics.

## How does a paid order reach its channel?

Through the person who paid. Switch the website source to **Full** under **Data → Sources**, then set the tracker to full mode and let your banner pass the visitor’s answer with `mirafive('consent', …)`:

```yaml
# config/packages/mirafive.yaml
mirafive:
    script_mode: full
```

Identify the signed-in user in a `LoginSuccessEvent` listener, with the same internal id your server sends. Never send `getUserIdentifier()` when it is an email address:

```php
$this->mira->identify((string) $user->getId(), ['plan' => $user->getPlan()]);
```

The paid order then lands on that person, next to the channel of their first visit. Create a purchase goal for `order_paid`, and Acquisition shows buyers and revenue per channel and campaign.

## How do I make a webhook count once?

`send()` goes out at once with an idempotency key and returns a `MiraFive\Receipt`, so a repeat is stored once. It never goes through Messenger:

```php
$mira->send([
    ['name' => 'order_paid', 'userId' => (string) $order->getCustomerId(), 'properties' => ['revenue' => 328, 'currency' => 'EUR']],
], idempotencyKey: "order-{$order->getId()}");
```

## What else does the bundle handle?

- `mirafive_script({autocapture: true})` adds clicks, form submits and field changes without code. Every option maps to a tracker attribute, and an unknown one throws.
- `enabled: false` under `when@dev` records nothing, so local work stays out of your numbers.
- Delivery failures never reach your code. They are logged as warnings on the `mirafive` Monolog channel.

The source’s Install tab has **Let your coding agent install it**, a prompt for Claude Code, Cursor or Codex with every step and the check; for a server source it never contains the secret key. The [Symfony SDK docs](https://docs.mirafive.io/sdks/symfony) cover Messenger delivery and every option, and the [setup page](https://mirafive.io/setup) shows who does what, in order.

## Other setups

The same numbers, whichever way your site is built.

- [Astro](https://mirafive.io/for/astro)
- [Next.js](https://mirafive.io/for/nextjs)
- [React](https://mirafive.io/for/react)
- [Laravel](https://mirafive.io/for/laravel)
- [Nuxt](https://mirafive.io/for/nuxt)
- [PHP](https://mirafive.io/for/php)
- [Vue](https://mirafive.io/for/vue)
- [TanStack Start](https://mirafive.io/for/tanstack-start)
- [Node.js](https://mirafive.io/for/nodejs)
- [Convex](https://mirafive.io/for/convex)
- [JavaScript](https://mirafive.io/for/javascript)
- [any website](https://mirafive.io/for/website)

[Read the docs](https://docs.mirafive.io)

## Questions and answers

### Does tracking slow down my responses?

No. `track()` only buffers, and the bundle sends once per request on `kernel.terminate`, which under PHP-FPM runs after the response has been sent. With `messenger: true` and a routing entry, a Messenger worker sends instead.

### How do I test code that sends events?

Set `test: true` under `when@test` in `config/packages/mirafive.yaml`. Nothing touches the network, and `self::mira()->assertTracked('order_paid')` from the `InteractsWithMira` trait checks the event, including events still in the buffer.

### Does it work with FrankenPHP worker mode or RoadRunner?

Yes. Every service reset between two requests sends what is left in the buffer, and `messenger:consume` sends after each message. Per-request state lives on the request, so nothing leaks into the next one.

### Which versions are supported?

PHP 8.3 or later and Symfony 6.4, 7 and 8\. Twig and Messenger are optional. The bundle wraps the PHP SDK, `mirafive/sdk-php`.

## See which channel brings buyers

Free for 25,000 events a month. No card needed.

[Start free](https://app.mirafive.io/register)[See pricing](https://mirafive.io/pricing)
