mirafive/sdk-laravel
A Laravel SDK package that wraps Mirafive services in Laravel-native conventions, providing a service provider, facade, and publishable configuration so you can integrate Mirafive features into your app with minimal setup and fluent, expressive calls.
Three commands and you're live:
composer require mirafive/sdk-laravel
php artisan vendor:publish --tag=mirafive-config
php artisan mirafive:check
Package discovery registers MiraFiveServiceProvider and the Mira facade automatically — no config/app.php edits.
config/mirafive.php first. Everything you'll touch day-to-day lives there: the enabled switch, the server-event mode (default full), the tracker script_mode (default consentless), the flag cache store, and the queue toggle. Cross-reference against the environment variables in your .env — the secret key and website key are the two that matter most.<head>:
@mirafiveScript
It renders the hosted tracker with the website key and picks up the Vite CSP nonce automatically.use MiraFive\Mira;
Mira::forUser($user)->track(/* event payload */);
Nothing is transmitted inline — the SDK buffers and flushes on application terminate, so you can sprinkle tracking calls without adding latency to the request.php artisan mirafive:check sends the $install_check payload and prints the receipt. If that receipt looks right, your key, transport and config are wired. If it prints nothing, you're in disabled mode (see Gotchas).The first genuinely useful use case is a single server-side event on a high-value action (signup, checkout, upgrade) plus @mirafiveScript in the layout — that gets you both server and client coverage with about four lines of code.
Buffer-and-flush is the default mental model. You don't await network calls. The container-resolved Mira and MiraFlags instances accumulate work and are flushed at application terminate, after each Octane request/task/tick, and after each queued job. Write tracking calls as if they were fire-and-forget logging; the package makes them durable at the right lifecycle boundary.
Per-user scoping. Mira::forUser($user) is the idiomatic way to attach a distinct identity. Use it at the top of a controller or job rather than threading IDs through every call:
Mira::forUser($request->user())->track(/* ... */);
Consent gates belong early. Mira::optedOut($request) returns whether the current request has opted out — check it before you buffer anything, or wrap your tracking calls in a small helper/action so the check can't be forgotten in one of twenty call sites. This matters especially with the default script_mode of consentless; if you're operating under GDPR-style consent, switch script_mode and gate @mirafiveScript behind your own consent banner state.
Queued delivery for latency-sensitive paths. Set MIRAFIVE_QUEUE and each buffered batch is encoded at hand-off and pushed into a SendBatch job. The worker delivers it byte-for-byte with its own key — no re-serialization, no drift. This is the right default for anything writing to the queue already, and it keeps large fan-out events off the request cycle. Note that send() deliberately stays synchronous, so you still have an escape hatch for CLI commands and jobs where you want deterministic completion.
Flags as a shared document. The flag document is held in a Laravel cache store and shared between requests, so MiraFlags resolution is cheap after the first fetch. In Blade:
@mirafiveFlags($unit)
emits the bootstrap block and sets Cache-Control: private, no-store on the response. In PHP, resolve MiraFlags from the container the same way you resolve Mira.
Testing is first-class. Mira::fake() in your test's setUp swaps the implementation and gives you assertTracked, assertNotTracked, assertIdentified and assertNothingTracked. The assertNothingTracked assertion is the one to reach for in regression tests around consent and disabled mode — it catches accidental leakage.
Octane needs no special handling from you. Instances are warmed and flushed per request/task/tick, so the usual "state leaks across Octane requests" fear doesn't apply. Just resolve Mira from the container at call time rather than caching it in a singleton of your own.
Disabled mode is silent by design. With enabled false or no secret key configured, nothing is sent and no key is required — but input is still validated, so your code won't throw. That's excellent for local dev and CI, and dangerous in staging: a missing MIRAFIVE_SECRET_KEY in a new environment looks exactly like a working integration. Make php artisan mirafive:check a deploy smoke test and check the receipt, not the exit code.
The queued path needs the key on the worker, not the web node. The SendBatch job carries its own key and delivers byte-for-byte. If your web dynos have the secret but your queue workers don't (common with split env configs), the batch is encoded successfully and then fails at delivery. Verify env parity across both process types.
Payloads are frozen at hand-off. Because encoding happens before dispatch, a config change deployed between dispatch and delivery won't retroactively apply. That's the intended guarantee, but it also means a batch queued under a bad config is delivered under that bad config. Drain the queue when changing tracking config.
send() ignores MIRAFIVE_QUEUE. It's synchronous by contract. If you're calling it inside a loop thinking it's batched, you're making one network round-trip per call.
Terminate-time flushing has the usual terminate-time caveats. dd(), exit, die, unhandled fatals and some middleware short-circuits can skip the terminate hook. If you're debugging "the event never arrived," reproduce with send() in a tinker session or an artisan command before suspecting the transport.
@mirafiveFlags poisons full-page caching. It sets Cache-Control: private, no-store on the entire response. On a page you serve through Cloudflare, a CDN, or Laravel's response cache, that single directive disables caching for the whole document. Either put flags on a dedicated fragment/JSON endpoint that your frontend fetches, or accept the page is uncacheable. This is the most likely "why is our hit rate zero" bug you'll hit.
Flag staleness comes from your cache store. The document is shared through a configured Laravel cache store. On a shared Redis with a shared prefix across multiple apps or environments, you can read another app's flags. Use a dedicated store or prefix for mirafive, and know the TTL — flag changes won't appear until the cached document expires or you clear it.
CSP nonce is Vite-shaped. @mirafiveScript reads the Vite CSP nonce. If you've rolled a custom CSP middleware, render the script yourself with your own nonce or set the nonce where Vite expects it; otherwise the tracker is blocked and you'll see it only in the browser console, never in Laravel logs.
optedOut($request) is request-scoped. It answers for the current request, not for a stored user preference. If you persist consent on the user model, combine the two — the SDK call tells you what the request carries, your model tells you what the user decided last month.
Extension points worth knowing. The underlying mirafive/sdk-php exposes hand-off and deliverPrepared — that's the seam if you need custom transport, an alternate queue driver, or to persist prepared batches yourself for replay. flushOnShutdown lets you opt out of automatic flushing when you want to control exactly when delivery happens (useful in long-running commands and custom workers). Publish the config and you can point the flag document at a different cache store without touching package code.
How can I help you explore Laravel packages today?