mirafive/sdk-symfony
Mirafive SDK for Symfony offers a framework-friendly integration layer for the Mirafive PHP SDK, with bundle configuration and service wiring to help Symfony developers connect to Mirafive APIs quickly and reduce manual setup.
This is a Symfony bundle, not a Laravel package. Everything below assumes a Symfony app; on Laravel you would use mirafive/sdk-php core directly and there is no equivalent service provider, publishable config, or Artisan command here. Confirm this before you add it to a Laravel dependency list.
Minimal steps:
composer require mirafive/sdk-symfony
MiraFive\Symfony\MiraFiveBundle in config/bundles.php (it extends AbstractBundle, so config lives in the bundle's own tree rather than a Configuration class you hand-wire).config/packages/mirafive.yaml with secret_key, website_key, and host. Every key defaults to a MIRAFIVE_* environment variable, so in most deployments you configure env vars and leave the YAML minimal.bin/console mirafive:check
It sends a $install_check event and prints the receipt. If it prints nothing useful, you almost certainly have no secret key or enabled: false — see Gotchas.
First real use case, in order of value:
{{ mirafive_script() }} in your base layout (once, in <head>), or inject MiraFive\Mira and call it for server-side events.{{ mirafive_flags() }} in the layout for a client-side flag bootstrap, or autowire MiraFive\Flags\MiraFlags for server-side checks.MiraFive\Symfony\Test\MiraFake and the InteractsWithMira trait early — retrofitting assertions onto already-written tracking code is tedious.First places to look in the source: the bundle class itself (it wires all the flush listeners), the config tree in the notes above, and MiraFive\Mira::deliverPrepared() if you plan any custom delivery.
Autowire the two services, don't build them. MiraFive\Mira and MiraFive\Flags\MiraFlags are autowired. MiraFlags is effectively one instance per process — treat it as a shared singleton and don't expect per-request isolation in worker runtimes. Use $mira->flags() as the documented entry point rather than resolving flags separately.
Constructor injection over container pulls. public function __construct(private Mira $mira) {} in a service or controller. Avoid $container->get() so the enabled: false path stays consistent (see Gotchas).
Let the lifecycle flush, don't flush by hand. The bundle flushes on kernel.terminate, console.terminate, kernel.reset, and after each handled or failed Messenger message. The core SDK's own shutdown flush is deliberately disabled, so Symfony's lifecycle is the single source of truth. Normal request/response code never needs an explicit flush.
Long-running workers are the interesting case. Under Messenger consumers, RoadRunner, Swoole, or FrankenPHP, kernel.reset is your flush boundary between jobs. If you write a custom loop that doesn't go through kernel.reset, you are responsible for the flush — either dispatch through the normal kernel or drive the core seams (handOff, deliverPrepared()) yourself.
Async delivery via Messenger. Enable the messenger config, route the DeliverBatch message to a transport, and run a worker. The worker's own client delivers the buffered batch byte for byte, so payloads are identical to the synchronous path — no re-serialization surprises. Note the asymmetry: send() stays synchronous even when messenger is on. Async only applies to buffered batches, so if you need every event non-blocking, buffer rather than call send() inline.
Twig, sparingly and in the right places. mirafive_script() once in the base layout. mirafive_flags() only on pages that actually consume flags on the client — it stamps Cache-Control: private, no-store on the response, which is a meaningful decision, not an incidental header.
Configuration layering. Keep the YAML thin and push environment specifics to MIRAFIVE_* vars. The keys worth deliberate decisions: enabled (kill switch), mode / script_mode (how the tracker tag behaves), messenger, flags.refresh_seconds, flags.cache, and test. Wire test: true only in the test environment.
Feature-test with the fake, not mocks.
use MiraFive\Symfony\Test\InteractsWithMira;
class CheckoutTest extends KernelTestCase
{
use InteractsWithMira;
public function test_order_tracked(): void
{
// ... arrange, act
$this->assertMiraSent(/* whatever the trait exposes */);
}
}
The MiraFake records what the real client would have sent, which is far more robust than mocking Mira.
CI/deploy gate. Put bin/console mirafive:check --env=prod (or an equivalent smoke step) after cache warmup in the deploy pipeline. It's the cheapest way to catch a missing env var in a new environment.
config/mirafive.php to publish, and bin/console is not artisan. Use the core SDK's Laravel seams instead.enabled: false or no secret key, both Mira and MiraFlags record nothing — no exception, no log line. Every "tracking is broken" bug starts here. Run mirafive:check first; it gives you a receipt and answers the question in one command.mirafive:check is a real send. It emits $install_check against your configured host — run it against production deliberately, not casually from a local .env pointing at prod.mirafive/sdk-php may add manual flushes out of habit. The core shutdown flush is off here precisely so you don't double-send on terminate. Remove manual flush calls before debugging a duplicate-batch report.flags.refresh_seconds in workers. MiraFlags is one instance per process; in a long-lived worker a short refresh means repeated fetches and a long refresh means stale flags for the life of the process. kernel.reset may not be enough to force a re-evaluation — verify empirically if flag flips matter to you. flags.cache is a separate layer; tune both together, not one at a time.mirafive_flags() and HTTP caching. The private, no-store header it sets will make any page it touches uncacheable at shared proxies and CDNs. Don't drop it in a global layout template if only two pages need flags — scope it.send() is synchronous, always. Even with messenger enabled. Watch for it in loops, in queued jobs whose latency budget is tight, and in request paths where a third-party timeout becomes your timeout. Buffered batches are the escape hatch.DeliverBatch silently backs up in the transport if the worker is down; batches arrive late, not lost, but your dashboards are wrong in the meantime. Monitor transport depth like any other queue.secret_key and enabled: false should be distinguishable in your own code. If you have logic that branches on "tracking enabled," don't read it from the SDK's behavior (it's silent) — read your config explicitly.enabled, flushOnShutdown, flagsRefreshSeconds, handOff, deliverPrepared() — are how you take control in a non-standard worker loop. deliverPrepared() hands you an already-built batch, which is the right level to hook if you want to route deliveries through your own transport or add retry/idempotency semantics. Don't fork the bundle; compose around these.test: false leaks into the test environment, MiraFake never installs and the real client sends from CI. Assert once in a smoke test that the fake is active.How can I help you explore Laravel packages today?