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

Ami Laravel Package

enniel/ami

PHP client for the Asterisk Manager Interface (AMI). Connect to an Asterisk PBX, send manager actions, and handle responses and asynchronous events through a simple, framework-agnostic API. Works standalone or within Laravel applications.

Deep Wiki
Context7

Getting Started

Verify the package is actually installable before committing to it. It was last released in Feb 2017 and is archived, so its composer.json almost certainly caps PHP at ^5.6|^7.0 and Laravel at 5.x. On a modern app you will need to either fork it (add "enniel/ami": "dev-master" from your fork with a widened constraint) or, better, port its handful of classes into your own app/Support/Ami namespace and depend on a maintained AMI transport instead.

composer require enniel/ami
  1. Find the provider. There is a service provider (typically Enniel\Ami\AmiServiceProvider) registered via package discovery or config/app.php. Read src/ first — the package is small enough that the provider, the connection class, and the action/event classes are the whole story. Do not trust the README over the source; a 2017 README will not mention the Laravel versions you are on.
  2. Publish the config. php artisan vendor:publish and pick the ami tag (or pass --provider=). You want config/ami.php in your repo so ops can see the connection settings.
  3. Wire credentials through env. The config will read something like AMI_HOST, AMI_PORT (default 5038, TLS 5039), AMI_USERNAME, AMI_SECRET. Never commit the secret; these are manager-account credentials from Asterisk's manager.conf.
  4. Smoke-test with the cheapest possible action. A Ping or a Login that returns Response: Success / Message: Authentication accepted proves host, port, credentials, and firewall in one shot. Do this from a tinker session or a one-off artisan command before writing any feature code:
// tinker
app('ami')->action('Ping'); // exact method name depends on the package's API — check src/

First real use case: a "who's on the phone right now" panel. Call a single queue-status action (QueueStatus) and render the result. It exercises login, action dispatch, response parsing, and gives your stakeholders something visible on day one. Only after that works should you tackle the event stream.

Implementation Patterns

Wrap it, don't sprinkle it. Resolve the package from the container in exactly one place — a TelephonyService or AmiClient interface — and have the rest of the app depend on that interface. Given the package's age, you will need to swap the implementation within a year, and this is the difference between a two-hour migration and a two-week one.

interface TelephonyService
{
    public function queueStatus(string $queue): array;
    public function originate(string $ext, string $dest): void;
    public function pauseAgent(string $interface, bool $paused): void;
}

Action-per-intent, not raw strings. AMI is a line-oriented text protocol (Action: QueueAdd\r\nQueue: 500\r\n...). The package exposes either an action() helper or discrete classes. Either way, define your own thin methods (pauseAgent(), originate()) so callers never assemble AMI headers by hand. Centralizing this also gives you one place to add ActionID correlation logging.

Event consumption belongs in a worker, never in an HTTP request. AMI pushes events (Newchannel, AgentCalled, AgentConnect, Hangup, QueueMemberPaused, …) asynchronously and continuously. Build an artisan command that connects once, logs in with the event mask you need, and loops forever reading frames; run it under Supervisor alongside queue:work.

// app/Console/Commands/AmiListen.php — sketch
while (true) {
    try {
        $this->ami->listen(function ($event) {
            CallEventReceived::dispatch($event); // your own domain event
        });
    } catch (ConnectionException $e) {
        $this->error($e->getMessage());
        sleep(5); // backoff, then reconnect
    }
}

Then fan out: a CallEventReceived listener writes calls rows, and a ShouldBroadcast variant pushes to a wallboard over Echo. This is the pattern that makes the package earn its place — AMI events become ordinary Laravel events.

Click-to-call via Originate is the other high-value integration. Put it behind a queued job with $tries = 1 and a strict timeout so a dead PBX cannot hang a web request. Always pass a unique ActionID and a caller ID your dialplan recognises.

Subscribe narrowly. Ask for only the event classes you consume (Events: call,agent or an explicit list). A default-login socket on a busy PBX will drown your parser in VarSet, Newexten, RTCPSent noise and you will spend a day wondering why your consumer lags.

Testing: fake the interface in feature tests and assert on dispatched commands/jobs. For the AMI layer itself, keep a set of recorded AMI frames (raw text, \r\n-delimited) as fixtures and test the parser against them. Never point CI at a real PBX. Reserve one manual/staging check against a pjsip/chan_sip test box for the release gate.

Gotchas and Tips

  • Maintenance is the headline risk. Archived since 2017, 28 stars, one release. Expect PHP 8 fatal errors from removed helpers (array_get, str_*), missing return types, and Laravel 9+ container/event-dispatch signature changes. If you adopt it, adopt it as a fork you own. Budget a day to modernize the source before you build features on it.
  • AMI is cleartext TCP. Credentials and caller data cross the wire in the clear. Bind manager to localhost or a private network, or use the TLS port plus manager.conf TLS settings. Never expose 5038 to the internet — manager accounts can originate calls and read CDRs.
  • Blocking sockets are the number-one production failure. PHP's default stream timeouts are effectively infinite. Set explicit default_socket_timeout / stream context timeouts, and put an outer watchdog on your listener command. A wedged listener is silent — it just stops writing rows.
  • Response/event interleaving. AMI does not guarantee your action response arrives next; events can appear first. If the package correlates by ActionID you're fine; if it naively reads "the next frame", you have a latent bug on busy systems. Verify in src/ and patch if needed.
  • "Success" is not always success. Login returns Response: Success with Message: Authentication accepted, but many commands return Response: Error inside an otherwise normal stream, and Follows: Yes responses need an explicit --END COMMAND-- terminator consumed before you can send the next action. Handle these three cases explicitly.
  • manager.conf permissions are the usual 3pm panic. Read/write classes (read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan; write = call,agent,user,config,command,reporting,originate) gate what your code can do. A Permission denied in the response means fix the dialplan-side config, not your PHP.
  • Config caching trap. If the config file calls env() at runtime outside the config array, php artisan config:cache will silently freeze or blank your credentials. Keep env() only inside config/ami.php.
  • Connection-per-request is simple but expensive. AMI login has a handshake and Asterisk limits concurrent manager sessions (manager show connected). For low volume, connect per request; for anything chatty, hold a single long-lived connection in the listener and a small pool in your service class.
  • Debugging toolkit: nc $AMI_HOST 5038 and type Action: Login by hand — if raw telnet works, the problem is your code; if it doesn't, it's firewall/Asterisk. Then asterisk -rx "manager show connected", asterisk -rx "manager show eventq", and a tcpdump -A port 5038 to eyeball frames. Log raw frames behind a debug flag; you will need them.

Extension points worth using: the published config file (per-environment connections), the container binding (swap in your own implementation or a mock), the package's event/hook if one exists (forward AMI events into Laravel's dispatcher rather than polling), and the action class base if it has one (subclass for custom Action: verbs your dialplan needs). Everything else, keep outside the package.

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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo