Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Zabbix Bundle Laravel Package

flowti/zabbix-bundle

flowti/zabbix-bundle offers a PHP integration layer for Zabbix, letting your application query hosts, items, triggers, and events through the Zabbix API. It wraps common monitoring calls in a simple, framework-friendly service so you can surface alerts and metrics without hand-writing raw API reques...

View on GitHub
Deep Wiki
Context7

Getting Started

The release notes change what this package is. The previous read was "Symfony bundle wrapping a Zabbix trapper sender." v0.0.1 ships an API client: user.login, user.logout, event.acknowledge, host.get, hostgroup.get, event.get, trigger.get. Those are JSON-RPC calls against https://<zabbix>/api_jsonrpc.php, not the TCP/10051 sender protocol. That matters for you because:

  • The Laravel story got much better. A Zabbix API client is HTTP + JSON. It's the easiest kind of vendor code to lift into app/Support/ — no DI container magic, no kernel events, no bundle wiring. The Symfony coupling is usually one constructor and a config array.
  • The use case changed. Pushing metrics is now a side note. The headline is reading and mutating monitoring state: current problems, host inventory, acknowledging events. That's a dashboard / ops-tooling package, not an instrumentation package.
  • Still not a Laravel package. No service provider, no config/zabbix.php, no facade. composer require flowti/zabbix-bundle into a Laravel app still installs files nothing loads. You are harvesting a class, deliberately.

Two jobs, decide before you plan:

  1. You want problems/hosts/events out of Zabbix into a Laravel app — vendor the API client class, bind it in a provider, wrap it in your own interface. This is now the main path.
  2. You want to push trapper metrics — the release notes say nothing about the sender path. Check src/ for a sender class before assuming it still exists or works; if it's absent or Symfony-bound, write the ~40-line protocol yourself.

Where to look first

  • src/DependencyInjection/Configuration.php — the config tree is the real docs. You're looking for api_url / username / password / auth / timeout, and whether a trapper host/port pair still exists.
  • The API client class in src/ — read its constructor and its request method. You need to know: does it user.login per call, per instantiation, or does it accept a pre-obtained auth token? That one detail decides how much glue you write.
  • Resources/config/services.* — the service ID to inject in Symfony, and the class name to lift for Laravel.
  • composer.json — Symfony version constraints and whether symfony/http-client / Guzzle is a hard dependency.

Minimal path (Symfony host app)

composer require flowti/zabbix-bundle
// config/bundles.php
Floti\ZabbixBundle\FlowtiZabbixBundle::class => ['all' => true],
# config/packages/flowti_zabbix.yaml  <- confirm root key + keys in Configuration.php
flowti_zabbix:
    api_url: '%env(ZABBIX_API_URL)%'
    username: '%env(ZABBIX_API_USER)%'
    password: '%env(ZABBIX_API_PASSWORD)%'
php bin/console debug:container zabbix

Minimal path (Laravel host app)

Copy the client class into app/Support/Zabbix/, strip the Symfony-only constructor arguments, bind it:

// app/Providers/ZabbixServiceProvider.php
public function register(): void
{
    $this->app->singleton(ZabbixApiClient::class, fn () => new ZabbixApiClient(
        apiUrl: config('services.zabbix.url'),
        user: config('services.zabbix.user'),
        password: config('services.zabbix.password'),
    ));
}

If the class fights you (event dispatcher, logger, parameter bag), don't fight back. The API is a single POST with a JSON body — this is the entire protocol:

$response = Http::timeout(5)->post(config('services.zabbix.url'), [
    'jsonrpc' => '2.0',
    'method'  => 'trigger.get',
    'params'  => ['output' => ['triggerid', 'description', 'priority'], 'only_true' => 1, 'min_severity' => 3],
    'id'      => 1,
    'auth'    => $token, // or omit and use a pre-issued API token
]);

First real use case: a "current problems" panel — trigger.get with only_true + min_severity, joined to host.get for host names, cached 60s, plus an event.acknowledge action from the admin UI. That's the 80% case for this release and it exercises every listed method except the ones you'll never call.

Implementation Patterns

Authenticate once, cache the token. user.login returns a session auth token with a server-side TTL. Logging in on every client instantiation is a needless round trip and a needless audit-log entry. Log in lazily, stash the token in Cache::remember('zabbix.auth', now()->addMinutes(25), ...), and on an auth error (JSON-RPC error code for "not authorised") invalidate and retry exactly once.

Don't call user.logout per request. In a PHP-FPM/Laravel world, logout on shutdown buys nothing and turns a 3-request page into 4. Let the token TTL expire; only wire logout into a long-running process's shutdown if you're genuinely session-constrained.

Wrap the raw client in a narrow, intention-revealing interface. The package returns untyped nested arrays with string IDs. Never let those reach a controller.

interface Problems
{
    /** @return Problem[] */  // your DTO, not the raw array
    public function current(int $minSeverity = 3): array;

    public function acknowledge(string $eventId, string $comment): void;
}

This is also your seam for tests and for swapping to a hand-rolled HTTP client later, which you may well do.

Poll, cache, serve. trigger.get and event.get hit the Zabbix database directly. A scheduled command every 30–60s writing into cache/Redis, with the web layer reading only cache, is the only pattern that survives real traffic. Never call the API from a page render.

Queue writes. event.acknowledge mutates state on a system other teams depend on. Dispatch a job, return 202, surface the result via the job's own notification path. You do not want a Slack ack button blocked on Zabbix API latency.

Resolve host IDs once and memoise. host.get output is hostid (string), host (technical name), name (visible name), status (0 monitored / 1 not), interfaces only if you asked via selectInterfaces. Build a host → hostid map, cache it for hours, and refresh on a schedule. It almost never changes and it's the join key for everything else.

Batch where you can. JSON-RPC 2.0 accepts an array of request objects in one POST and returns an array of responses — same shape, per-call errors. If the bundle's client doesn't expose that, and you're fetching problems for 30 hosts, you'll feel it.

Keep the two integrations separate in your head. Pull (this package, HTTP 80/443 to the Zabbix frontend, read + acknowledge) and push (trapper sender, TCP 10051 to the server, write metrics) are different network paths, different auth models, different failure modes. Most mature setups use both; they share no code and should share no config key.

Fake the HTTP layer in tests. Http::fake() and assert on the request body — method name, only_true, limit, the action bitmask. Asserting only on stubbed responses hides the payload bugs, which are the ones that actually happen. Also assert that an error response doesn't blow up the caller.

Gotchas and Tips

v0.0.1 means zero BC promise. Pre-1.0 versioning, first tagged release, no dependents to sanity-check it. Pin an exact version in composer.json, and plan on forking — the license is MIT, so a maintained copy in app/Support/ is cheap and honest. This is a "vendor it" package, not a "depend on it" package.

user.login is the legacy auth path. Zabbix 6.4 introduced API tokens and deprecated the auth property; modern setups issue a token per integration and skip login entirely. Check whether the client can send a Bearer/pre-issued auth value — if it only knows user.login, you're wiring username+password for a monitoring integration, which is both a security smell and a forward-compatibility risk. Verify against the exact Zabbix version you run (6.0 LTS, 6.4, 7.0 all differ here).

HTTP 200 is not success. Zabbix returns errors as a JSON-RPC error object with code, message, data — status code is 200. Batch requests return an array where some elements are errors. Any client that returns the decoded array without checking error will silently hand you null where you expected problems. Check error on every response, log it, and throw your own exception type.

event.acknowledge uses an action bitmask, not a string. 1 = close problem, 2 = acknowledge, 4 = add message, 8 = change severity, 16 = unacknowledge. So "acknowledge with a comment" is action: 6 (2 + 4). Send action: 2 and your comment vanishes with no error. Send action: 6 without message and you may get an error or an empty comment depending on version. This is the single most common bug in Zabbix integrations.

event.acknowledge can succeed emptily. It returns eventids — an empty array means nothing matched (wrong ID, or already acknowledged and not re-acknowledgeable). No exception, no warning. Treat empty eventids as a failure in your code.

trigger.get defaults are expensive and surprising. No only_true means you get OK triggers too. min_severity defaults to 0. output: extend returns every field. active, monitored, skipDependent all change the result set. Always pass explicit output (field list), only_true, min_severity, and limit.

event.get without time_from and limit will hammer the Zabbix DB. It's a time-series table. Pass time_from as a Unix timestamp — string datetimes are interpreted in the Zabbix server's timezone and will quietly be hours off — plus value: 1 for problems, source: 0 for trigger events, and a hard limit. Watch the API user's timeout; long queries get killed, not paginated.

IDs are strings. hostid, eventid, triggerid all come back as strings. Strict comparisons (===) against ints fail, and using them as array keys silently casts. Normalise at your DTO boundary and never again.

host.get: host vs name. host is the technical name (what the trapper host field must match, what macros use); name is the visible name. Getting these backwards is the classic "my metrics land on the wrong host" bug. status: 0 means monitored — 1 means unmonitored, which reads backwards to almost everyone.

Different port, different firewall rule. The API is HTTPS to the frontend on 80/443. The trapper is outbound 10051 to the server. If someone whitelisted 10051 and the API client times out, that's why. Test from the container: curl -sS $ZABBIX_API_URL -d '...'.

Least privilege on the API user. Create a dedicated user with a role that can read hosts/triggers/events and acknowledge events — not a super-admin. Credentials in config/services.php via env, never committed; the package's YAML config may invite inlining them.

Config is cached, twice over. Symfony compiles the container (cache:clear after YAML edits); Laravel caches config (config:cache). Debugging a config that isn't being read wastes an afternoon in both ecosystems.

Timeout and retry live in your wrapper, not the vendor class. Set an explicit 5s timeout, retry once on connection error only (not on JSON-RPC errors — those are deterministic), and never let a Zabbix outage throw out of a page render.

Instrument the client itself. Log method, request id, duration, and any error.code at debug. When a dashboard goes blank, the difference between "auth expired", "role lacks permission", and "trigger.get returned zero rows" is in that log line.

Extension points if you stay on Symfony: decorate the client service (same class, your wrapper, constructor-injected original) to add logging/retry, or override the service definition ID in your app's services.yaml. Both survive composer update; editing vendor/ does not. In Laravel, the extension point is your provider binding — which is exactly why the provider is worth writing even for a one-class harvest.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route