Laravel opcua
Laravel 11/12/13 integration for OPC UA. Provides a Facade (Opcua::*), service provider, .env-based named connections, an Artisan daemon command (opcua:session), and transparent session persistence via the opcua-session-manager daemon. Use this skill whenever the user is working with OPC UA from a Laravel application — controllers, jobs, Livewire components, Filament panels, broadcasting, Horizon queues, Octane workers, scheduled tasks, or Pest tests.From its SKILL.md
npx -y skills add php-opcua/ai-skills --skill laravel-opcuaAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
11.7 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it
laravel-opcua
A thin, idiomatic Laravel layer over php-opcua/opcua-client. Three things to remember:
- The Facade
PhpOpcua\LaravelOpcua\Facades\Opcuaproxies the fullOpcUaClientInterface. Anythingopcua-clientcan do, the Facade can do. OpcuaManager::shouldUseSessionManager()decides per-call whether to instantiate a directClient(TCP straight to the server) or aManagedClient(IPC to the long-lived daemon). The decision is transparent to application code.- v4.4.0 picked up 21 new client methods (HistoryUpdate, File transfer, Aggregates). They are reachable through
Opcua::*andOpcua::connection('plc-1')->*without any config or service-provider change.
What this package is for
| You want to | Use |
|---|---|
| Read / write OPC UA nodes from a controller, job, command | Opcua::read(), Opcua::write() (Facade) |
| Talk to multiple OPC UA servers | Named connections in config/opcua.php, Opcua::connection('plc-1') |
| Connect to a runtime-discovered endpoint | Opcua::connectTo($url, $configOverrides, as: 'cache-key') |
| Avoid one new TCP connection per HTTP request | Run php artisan opcua:session as a supervised daemon |
| React to data changes, alarms, etc. via PSR-14 → Laravel Event system | Configure auto_publish: true + auto_connect: true + subscriptions: [...] |
| Test code that touches OPC UA without a server | PhpOpcua\Client\MockClient + Facade swap, see references/TESTING.md |
| Stream notifications to Livewire / Broadcasting / Notifications / Filament | Register listeners on DataChangeReceived, AlarmActivated, etc. (see references/INTEGRATIONS.md) |
Mental model
Application code
└── Opcua::* (Facade)
└── OpcuaManager::connection($name)
├── shouldUseSessionManager() == true?
│ └── ManagedClient (IPC → daemon → TCP → server)
│ └── TransportFactory picks UnixSocketTransport (Linux/macOS) or TcpLoopbackTransport (Windows)
└── shouldUseSessionManager() == false?
└── ClientBuilder::create()->...->connect() (direct TCP, new connection per call)
The two branches expose the same OpcUaClientInterface. Your code does not know which it has.
Quick start
composer require php-opcua/laravel-opcua
php artisan vendor:publish --tag=opcua-config
# .env
OPCUA_ENDPOINT=opc.tcp://plc.example:4840
OPCUA_USERNAME=operator
OPCUA_PASSWORD=changeme
OPCUA_SECURITY_POLICY=Basic256Sha256
OPCUA_SECURITY_MODE=SignAndEncrypt
use PhpOpcua\LaravelOpcua\Facades\Opcua;
Opcua::read('i=2259')->getValue(); // 0 = Running
Opcua::write('ns=2;s=Setpoint', 42.5); // auto-detects Double
Opcua::browseRecursive('i=85', maxDepth: 3);
The 3 patterns you will use 90% of the time
Pattern A — one-shot read/write (no daemon)
Best for HTTP requests, scheduled jobs, Artisan commands. The Facade opens a TCP connection per call, reads/writes, then closes.
public function showServerState(): array
{
$state = Opcua::read('i=2259')->getValue();
return ['state' => $state, 'running' => $state === 0];
}
Pattern B — daemon-backed, transparent session reuse
When you run php artisan opcua:session under Supervisor/systemd, every Facade call goes through the daemon. Sessions are reused; you no longer pay the connect + create-session + activate-session round-trip per request.
Run the daemon:
php artisan opcua:session --log-channel=stack --cache-store=redis
Application code does not change. Same Opcua::read(...), but now backed by ManagedClient automatically.
Pattern C — auto_publish + Laravel events
Subscribe declaratively in config; receive notifications as Laravel events.
// config/opcua.php
'session_manager' => ['auto_publish' => true],
'connections' => [
'plc-1' => [
'endpoint' => 'opc.tcp://plc.example:4840',
'auto_connect' => true,
'subscriptions' => [[
'publishing_interval' => 500.0,
'monitored_items' => [
['node_id' => 'ns=2;s=Temperature', 'client_handle' => 1],
],
]],
],
],
// app/Providers/EventServiceProvider.php
use PhpOpcua\Client\Event\DataChangeReceived;
Event::listen(DataChangeReceived::class, function (DataChangeReceived $e) {
SensorReading::create([
'client_handle' => $e->clientHandle,
'value' => $e->dataValue->getValue(),
'sampled_at' => $e->dataValue->sourceTimestamp,
]);
});
The daemon's auto-publish loop dispatches PSR-14 events through Laravel's event dispatcher. Listeners can be queued, broadcast, etc. — see references/INTEGRATIONS.md.
Facade method surface (one-line summary)
Connection management: connection(), connect(), connectTo(), disconnect(), disconnectAll(), isSessionManagerRunning(), getDefaultConnection().
Proxied to the active connection (auto-routed via __call):
- Reading:
read,readMulti - Writing:
write,writeMulti - Browsing:
browse,browseAll,browseRecursive,browseWithContinuation,browseNext,resolveNodeId,translateBrowsePaths - Method calls:
call - Subscriptions:
createSubscription,createMonitoredItems,createEventMonitoredItem,modifyMonitoredItems,setTriggering,deleteMonitoredItems,deleteSubscription,publish,transferSubscriptions,republish - History read:
historyReadRaw,historyReadProcessed,historyReadAtTime - History update (v4.4):
historyInsertData,historyReplaceData,historyUpdateData,historyDeleteRawModified,historyDeleteAtTime,historyInsertEvent,historyReplaceEvent,historyUpdateEvent,historyDeleteEvent - File transfer (v4.4):
openFile,closeFile,readFile,writeFile,getFilePosition,setFilePosition,createDirectory,createFileInDirectory,deleteFileSystemObject,moveOrCopyFileSystemObject - Aggregates (v4.4):
aggregate,historyAggregate - Trust store:
trustCertificate,untrustCertificate,getTrustStore,getTrustPolicy - Discovery:
getEndpoints,discoverDataTypes,getExtensionObjectRepository - Cache / logging:
getLogger,getCache,invalidateCache,flushCache - Connection state:
connect,disconnect,reconnect,isConnected,getConnectionState,getTimeout,getAutoRetry,getBatchSize,getDefaultBrowseMaxDepth,getServerMaxNodesPerRead,getServerMaxNodesPerWrite
Full PHPDoc with all signatures: src/Facades/Opcua.php.
When to follow the references
Progressive disclosure — only load what the task needs:
references/CONFIG.md— everyconfig/opcua.phpkey, env vars, named connections, defaults, version-specific keysreferences/SESSION_MANAGER.md— daemon command, Supervisor/systemd setup, IPC endpoints, auto-publish vs manual publish, monitoringreferences/EVENTS.md— full list of 56 PSR-14 events, payload shapes, queued listener pattern, common listener recipesreferences/INTEGRATIONS.md— Octane/FrankenPHP, Horizon/queues, Livewire, Filament, Broadcasting, Notifications, Telescope/Pulsereferences/SECURITY.md— policies, modes, trust store, certificate auto-generation, X.509 user auth, env-driven configreferences/TESTING.md— Pest setup, MockClient + Facade swap, integration tests with Docker test-suitereferences/PITFALLS.md— common gotchas: facade in config files, Octane state, mixed daemon versions, etc.assets/recipes.md— copy-pasteable code snippets for the 15 most common end-to-end tasks
Idiomatic patterns
-
Inject
OpcuaManager, not the Facade, in long-lived classes. The Facade resolves the manager every call; injection caches it.public function __construct(private OpcuaManager $opcua) {} public function handle(): void { $this->opcua->read(...); } -
Use named connections per server. Don't string-build endpoints in code. Define
plc-1,plc-2,historianin config, thenOpcua::connection('historian')->historyReadRaw(...). -
Don't disconnect in HTTP requests when the daemon is enabled.
ManagedClient::disconnect()closes the daemon-side session, undoing the connection pooling. Let the session manager handle lifecycle. -
For auto-published subscriptions, never call
publish()yourself. It returnsauto_publish_activeerror. Subscribe to events instead. -
Use
useCache: falsefor fresh reads of high-churn nodes. The read metadata cache is the default — passrefresh: trueto bypass. -
Queue listeners for heavy event handling. A
DataChangeReceivedlistener that hits a database should beShouldQueue. Otherwise the daemon publish loop blocks on it. -
Opcua::connectTo()is for ad-hoc; cache by name with theas:parameter when reused across the request. -
Trust store goes on disk, not in DB.
storage/app/opcua-trust-store/by default; check it into a deploy volume, not git. -
Run the daemon under a dedicated UID with
socket_mode: 0600. The Facade-side process must be in the same group/UID. -
In Octane, configure
OpcuaManageras request-scoped via flushed singletons — seereferences/INTEGRATIONS.mdfor theOctaneServiceProvider::tickhook.
Exit codes (Artisan opcua:session)
| Code | Meaning |
|---|---|
| 0 | Daemon exited cleanly (SIGTERM/SIGINT) |
| 1 | Configuration error (invalid socket_path, missing required key) |
| 2 | Bind failure (port in use, socket-path EACCES, parent dir missing) |
| 3 | Runtime error inside daemon loop (logged via PSR-3 channel) |
Non-zero exits should be caught by Supervisor autorestart=true or systemd Restart=on-failure.
Versioning
The Laravel package versions lock-step with php-opcua/opcua-client and php-opcua/opcua-session-manager. Always upgrade in this order:
- Daemon first. Stop
opcua:session,composer update, restart. - Application second.
composer update php-opcua/laravel-opcua.
If you upgrade application before daemon and call a v4.4 method (e.g. historyInsertData), ManagedClient::__call() will fail with BadMethodCallException because the daemon has no handler for it.
What this skill does NOT cover
- The raw OPC UA protocol — see the
opcua-clientskill. - The session-manager daemon's IPC protocol — see the
opcua-session-managerskill. - CLI usage — see the
opcua-cliskill. - Companion-spec types (DI, IA, AutoID, etc.) — see the
opcua-client-nodesetskill.
Cross-skill workflow example (docs/recipes/persistent-tag-history.md):
opcua-cli generate:nodeset Vendor.NodeSet2.xml ...(nodeset skill)- App reads typed nodes via
Opcua::read()(this skill) - Persists into a historian via
Opcua::historyInsertData()(this skill + opcua-client) - A Filament panel browses results (this skill + Filament integration)
What ships with it: 8 files
73.8 KB alongside SKILL.md
assets/
- recipes.md13.8 KB
references/
- CONFIG.md10.3 KB
- EVENTS.md9.3 KB
- INTEGRATIONS.md8.5 KB
- PITFALLS.md7.8 KB
- SECURITY.md8.0 KB
- SESSION_MANAGER.md7.7 KB
- TESTING.md8.4 KB