inverge / nexus
Official PHP SDK for the Inverge Nexus platform — realtime, events, errors, logs, feature flags, links, sessions. Framework-agnostic with first-class Laravel and Symfony integrations.
Requires
- php: >=8.1
- ext-curl: *
- ext-json: *
Requires (Dev)
- monolog/monolog: ^3.0
- phpunit/phpunit: ^10.5
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
Suggests
- illuminate/support: Laravel integration (auto-discovered service provider + facade).
- monolog/monolog: Forward your app's logs to Nexus via Inverge\Nexus\Monolog\NexusLogHandler (^3).
- psr/http-client: Bring your own PSR-18 HTTP client (e.g. Guzzle) instead of the built-in cURL transport.
- symfony/http-kernel: Symfony integration (bundle + auto error-capture listener).
- symfony/messenger: Symfony async telemetry delivery (nexus: async: true).
Provides
None
Conflicts
None
Replaces
None
README
inverge/nexus is the server‑side client for the Inverge Nexus platform:
realtime messaging, product analytics, structured logging, error monitoring,
sessions, feature flags, remote config, deep‑link attribution, and surveys —
from plain PHP, Laravel, or Symfony.
- Package:
inverge/nexus - PHP:
>= 8.1
Table of contents
- Installation
- Plain PHP setup
- Laravel setup
- Symfony setup
- Dispatchers (sync vs queued)
- Sessions
- Events
- Logs
- Errors
- Feature flags
- Remote Config
- Deep links & attribution
- Realtime
- Surveys
- Monolog handler
- Low‑level request
1. Installation
composer require inverge/nexus
2. Plain PHP setup
use Inverge\Nexus\NexusClient; use Inverge\Nexus\Config; $nexus = new NexusClient(new Config( apiKey: 'nxs_live_xxx', baseUrl: 'https://services.inverge.net', // optional timeout: 10.0, // optional (seconds) defaultHeaders: [], // optional )); $nexus->events()->capture('order_placed', ['total' => 42.0], ['distinctId' => 'user_1']);
Config fields: apiKey (required), baseUrl, timeout, defaultHeaders.
Config::fromArray('nxs_…', [...]) is also available.
Resources: realtime(), events(), errors(), logs(), sessions(),
flags(), links(), surveys(), remoteConfig(). Plus config() and
request().
3. Laravel setup
The service provider auto‑registers. Publish config if you want to tweak it:
php artisan vendor:publish --tag=nexus-config
.env:
NEXUS_API_KEY=nxs_live_xxx NEXUS_BASE_URL=https://services.inverge.net NEXUS_TIMEOUT=10 NEXUS_QUEUE=false # true → dispatch telemetry on the queue NEXUS_QUEUE_CONNECTION= NEXUS_QUEUE_NAME= NEXUS_LOGGING=false # true → forward Laravel logs to Nexus NEXUS_LOG_LEVEL=debug NEXUS_CAPTURE_ERRORS=true # report unhandled exceptions automatically
Use the facade anywhere:
use Inverge\Nexus\Laravel\Nexus; Nexus::realtime()->emit('orders:42', 'status', ['state' => 'shipped']); Nexus::events()->capture('order_placed', ['total' => 42], ['distinctId' => auth()->id()]); Nexus::remoteConfig()->all(['userProperties' => ['governorate' => 'Erbil']]);
Queued variant (offloads the HTTP call to a job):
app('nexus.queue')->events()->capture('order_placed', ['total' => 42], ['distinctId' => $userId]);
Unhandled exceptions are reported automatically when NEXUS_CAPTURE_ERRORS=true.
4. Symfony setup
Register the bundle and configure it:
# config/packages/nexus.yaml nexus: api_key: '%env(NEXUS_API_KEY)%' base_url: 'https://services.inverge.net'
Inject NexusClient via autowiring; an exception subscriber reports uncaught
exceptions, and a Messenger handler is available for async dispatch.
5. Dispatchers (sync vs queued)
The client sends requests through a dispatcher. Default is synchronous cURL.
Swap it for a queued one (Laravel QueueDispatcher, Symfony
MessengerDispatcher) so telemetry never blocks the request:
$queued = $nexus->withDispatcher($myDispatcher); // returns a new client
You can also inject a custom PSR‑18 transport: new NexusClient($config, $psr18Transport).
6. Sessions
// Identify an end‑user: $nexus->sessions()->identify('user_123', [ 'email' => 'a@b.com', 'name' => 'Ada', 'traits' => ['plan' => 'pro'], ]); // Start/refresh a session: $nexus->sessions()->track([ 'distinctId' => 'user_123', 'deviceKey' => 'dev_abc', 'sessionKey' => 'sess_abc', 'country' => 'IQ', ]);
Returns the server payload (incl. sessionId).
7. Events
// One event → returns the number written (0/1): $nexus->events()->capture('order_placed', ['total' => 42.0, 'currency' => 'USD'], [ 'distinctId' => 'user_1', 'sessionKey' => 'sess_1', ]); // A batch of events sharing one identity/context: $nexus->events()->batch([ ['name' => 'view', 'properties' => ['sku' => 'A1']], ['name' => 'click', 'properties' => ['sku' => 'A1']], ], ['distinctId' => 'user_1']);
capture(string $name, array $properties = [], array $context = []): int.
Context keys: distinctId, sessionKey, deviceKey, release, osType, osVersion, browser, appVersion, timestamp.
8. Logs
$nexus->logs()->info('payment started', ['source' => 'checkout']); $nexus->logs()->error('charge failed', ['context' => ['code' => 'declined']]); // Generic + all levels: trace, debug, info, warn, error, fatal $nexus->logs()->log('warn', 'retrying', ['context' => ['attempt' => 2]]); // Batch: $nexus->logs()->batch([ ['level' => 'info', 'message' => 'a'], ['level' => 'error', 'message' => 'b'], ], ['distinctId' => 'user_1']);
Each level method: info(string $message, array $options = []): int. Options
include source, context, and the usual identity keys.
9. Errors
// From a caught throwable (recommended): try { doWork(); } catch (\Throwable $e) { $nexus->errors()->captureException($e, [ 'handled' => true, 'level' => 'error', 'context' => ['feature' => 'checkout'], 'distinctId' => 'user_1', ]); } // Or a manual error: $nexus->errors()->capture('Payment gateway timeout', [ 'type' => 'GatewayTimeout', 'level' => 'error', 'fingerprint' => 'gateway-timeout', ]);
Options: type, level, handled, fingerprint, stack, context, release, url, distinctId, sessionKey, deviceKey, osType, osVersion, browser, appVersion.
10. Feature flags
$nexus->flags()->isEnabled('new_checkout', 'user_1', ['plan' => 'pro']); // bool $nexus->flags()->variant('paywall', 'user_1'); // ?string $nexus->flags()->payload('paywall', 'user_1'); // mixed $all = $nexus->flags()->evaluate('user_1', ['plan' => 'pro']); // full result
evaluate(string $distinctId, array $properties = []): array returns every
flag; the others are convenience wrappers.
11. Remote Config
Fetch the active, published template resolved for a context (conditions — platform, version, country, percentile, custom attributes — evaluated server‑side).
// Flat key => value map: $config = $nexus->remoteConfig()->all([ 'appVersion' => '2.1.0', 'country' => 'IQ', 'userProperties' => ['governorate' => 'Duhok'], ]); $phone = $config['phone_number']; // A single value with a default: $phone = $nexus->remoteConfig()->get('phone_number', '+9640000000000', [ 'userProperties' => ['governorate' => 'Erbil'], ]); // Full result (version, etag, per‑parameter value + which condition supplied it): $result = $nexus->remoteConfig()->fetch( ['userProperties' => ['governorate' => 'Duhok']], $previousEtag, // optional If-None-Match; result['notModified'] === true when unchanged ); // $result['parameters']['phone_number'] === ['value' => ..., 'valueType' => 'STRING', 'source' => 'Duhok']
Context keys: appInstanceId, appVersion, appBuild, platform, osVersion, country, language, firstOpenTime, userProperties. userProperties values must
be primitives (strings/numbers/bools) and match condition values exactly.
fetch(array $context = [], ?string $etag = null): arrayall(array $context = []): arrayget(string $key, mixed $default = null, array $context = []): mixed
12. Deep links & attribution
$data = $nexus->links()->attribute('install', [ 'clickId' => 'abc', 'name' => 'summer_sale', 'platform' => 'ios', 'distinctId' => 'user_1', 'properties' => ['campaign' => 'promo'], ]);
attribute(string $type, array $options = []): array. Types: install, open,
reengagement, … Options include name, clickId, distinctId, deviceId, sessionKey, platform, osType, country, revenue, properties.
13. Realtime
Server‑to‑client fan‑out and room management over the data plane.
use Inverge\Nexus\RoomMessage; // Emit one or more events to a room: $nexus->realtime()->emit('orders:42', 'status', ['state' => 'shipped']); $nexus->realtime()->emit('orders:42', ['status', 'updated'], ['state' => 'shipped']); // Emit with a RoomMessage value object: $nexus->realtime()->emit(new RoomMessage('orders:42', ['status'], ['state' => 'shipped'])); // Batch many messages: $nexus->realtime()->broadcast([ new RoomMessage('room:a', ['ping'], ['n' => 1]), new RoomMessage('room:b', ['ping'], ['n' => 2]), ]); // Same payload to several rooms: $nexus->realtime()->emitToRooms(['a', 'b'], 'ping', ['n' => 1]); // Room management: $nexus->realtime()->registerRoom('orders', 'standard'); $rooms = $nexus->realtime()->rooms(); $nexus->realtime()->deleteRoom($roomId); $nexus->realtime()->related('orders'); // Link / unlink related rooms: $nexus->realtime()->link($roomId, $relatedId); $nexus->realtime()->unlink($roomId, $relatedId); // Payload schema (validation): $nexus->realtime()->setSchema($roomId, ['type' => 'object', 'required' => ['state']]); $nexus->realtime()->enableSchema($roomId, true); $nexus->realtime()->schema($roomId); $nexus->realtime()->clearSchema($roomId);
14. Surveys
// Surveys a user is eligible for (targeting/sampling/capping applied): $surveys = $nexus->surveys()->active([ 'distinctId' => 'user_1', 'properties' => ['plan' => 'pro'], 'osType' => 'ios', ]); // Submit a response (answers keyed by question id): $nexus->surveys()->respond('survey_1', ['q1' => 9, 'q2' => 'Great'], [ 'completed' => false, 'distinctId' => 'user_1', ]); // Convenience wrappers: $nexus->surveys()->complete('survey_1', ['q1' => 9], ['distinctId' => 'user_1']); $nexus->surveys()->dismiss('survey_1', ['distinctId' => 'user_1']);
15. Monolog handler
Forward your app's Monolog records to Nexus logs:
use Inverge\Nexus\Monolog\NexusLogHandler; $logger->pushHandler(new NexusLogHandler($nexus /*, level, bubble, flushAt */));
In Laravel, set NEXUS_LOGGING=true (and NEXUS_LOG_LEVEL) to wire this
automatically.
16. Low‑level request
For endpoints without a dedicated resource method:
$response = $nexus->request('POST', '/partner/events', ['events' => [...]], [ 'If-None-Match' => $etag, // extra headers ]);
request(string $method, string $path, ?array $json = null, array $headers = []): ?array
— returns the decoded body, or null on failure.