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

Opcua Client Laravel Package

php-opcua/opcua-client

A PHP client library for OPC UA, the industrial communication protocol used to exchange data with PLCs, SCADA systems and industrial devices. Browse, read and write nodes securely from your PHP applications.

Deep Wiki
Context7

Getting Started

The package is a pure-PHP OPC UA stack: one client object owns the TCP transport, secure channel, session, and service calls. That means your first job is simply to establish a session and read one node — everything else builds on that.

Requirements and install

  • PHP 8.1+ (check composer.json for the exact constraint).
  • ext-openssl for certificate/signing support; no C/C++ OPC UA SDK or extension required.
  • Install with Composer:
composer require php-opcua/opcua-client

Where to look first

  1. examples/ — usually contains a connect/read script and a subscription script. Copy one wholesale before reading the source.
  2. src/Client.php — the public facade for connect, read, write, browse, call, and disconnect.
  3. src/SecurityPolicy/ and src/Transport/ — only needed once anonymous/no-security works.
  4. tests/ integration fixtures — useful for seeing the exact NodeIds and endpoint style the maintainers test against.

Confirm the autoload namespace and constructor signature from composer.json and the example files; this package is small enough that the source is the definitive API reference.

Minimal first connection

<?php
require 'vendor/autoload.php';

use PhpOpcUa\Client\Client;

$client = new Client('opc.tcp://127.0.0.1:4840');

try {
    $client->connect();

    $value = $client->read('ns=2;s=Demo.Static.Scalar.Double');
    var_dump($value);

} finally {
    $client->disconnect();
}

First real use case

Pick one PLC tag you already know from UaExpert or the server’s tag browser. Hardcode its ns=2;s=... NodeId, read it once, and print the value. Once that works, switch to the namespace-array lookup described below so you are not hardcoding namespace indexes.

Implementation Patterns

Wrap the client in a long-lived service

OPC UA sessions are expensive. Do not connect per HTTP request or per queue job. Register one configured client per process and manage its lifecycle explicitly.

final class OpcUaService
{
    private ?Client $client = null;

    public function __construct(private array $config) {}

    public function client(): Client
    {
        if ($this->client === null) {
            $this->client = new Client($this->config['endpoint']);
            $this->client->setSecurityPolicy($this->config['security_policy']);
            $this->client->connect();
        }

        return $this->client;
    }

    public function close(): void
    {
        $this->client?->disconnect();
        $this->client = null;
    }
}

In Laravel, bind it as a singleton and configure it from config/opcua.php:

$this->app->singleton(OpcUaService::class, fn () => new OpcUaService(config('opcua')));

Batch reads instead of per-tag round trips

Most real integrations read dozens of tags per cycle. Use the array form so the client sends one Read service request rather than one TCP round trip per tag.

$nodes = [
    'ns=2;s=Line1.Speed',
    'ns=2;s=Line1.Temperature',
    'ns=2;s=Line1.Pressure',
];

$values = $client->read($nodes); // typically keyed by NodeId

Resolve the namespace index at runtime

Namespace indexes differ between servers and even between projects on the same server. Read the standard NamespaceArray node (i=2255) and map the URI to the index before building NodeIds.

$namespaceArray = $client->read('i=2255')->getValue();

$index = array_search('urn:my-company:line1', $namespaceArray, true);
$nodeId = "ns={$index};s=Line1.Speed";

Cache this mapping for the life of the session, not forever.

Browse to discover tags

When tags are configured dynamically or you are exploring a server, browse from the Objects folder (i=85) or a known folder node.

$references = $client->browse('ns=2;s=Line1');

foreach ($references as $reference) {
    echo $reference->getBrowseName() . ' => ' . $reference->getNodeId() . PHP_EOL;
}

Persist discovered NodeIds in your database or config after the first browse; do not walk the address space on every request.

Writes and data types

Always send the type the server expects. Writing a PHP integer into a Double tag or a scalar into an array tag is a common source of BadTypeMismatch.

$client->write('ns=2;s=Line1.Setpoint', 42.5, 'Double');

Method calls

Method calls are the normal way to trigger commands on a server — start a line, reset a counter, acknowledge an alarm.

$results = $client->call(
    'ns=2;s=Line1',
    'ns=2;s=Line1.ResetCounter',
    []
);

Inspect the result status code; a method can be invoked successfully but return a bad application-level status.

Subscriptions for change-driven data

Polling every tag on a timer is the wrong default. Use monitored items and a subscription when the server supports it.

$subscription = $client->createSubscription(1000); // publishing interval, ms

$subscription->monitor('ns=2;s=Line1.Speed', function ($value) {
    echo "Speed: {$value}\n";
});

$client->run(); // or the package's publish loop equivalent

For long-running workers, run a bounded publish loop inside a supervised process rather than an infinite loop in a web request.

Security configuration

Start with anonymous/no-security against a test server, then add:

  • Security policy name, e.g. Basic256Sha256.
  • Client certificate and private key paths.
  • Server certificate trust — many servers reject unknown clients until the client cert is trusted.
  • Username/password identity if the server does not allow anonymous.

Keep certificates outside the repository and inject paths via environment variables.

Gotchas and Tips

  • Endpoint URL must match what the server advertises. Some servers return a different endpoint in discovery responses; connect to the exact opc.tcp://host:port/path from the server config. If discovery fails, try bypassing it and connecting directly.
  • Do not hardcode ns=2. Namespace indexes are per-server and can change after a server project update. Always resolve via NamespaceArray.
  • Session timeouts bite long-running workers. Servers close idle sessions. Implement a keepalive read or reconnect on BadSessionIdInvalid/connection loss, and re-create subscriptions after reconnecting.
  • Reconnect does not restore subscriptions. Treat monitored items as state you must rebuild after any reconnect. Wrap subscription setup in a method you can call again.
  • Check status codes, not just exceptions. OPC UA frequently returns per-node StatusCodes rather than throwing. A batch read can partially succeed; inspect each result.
  • Data type mismatches are runtime errors. Confirm the server’s expected built-in type (Int16 vs Int32, Float vs Double, scalar vs array) before writing.
  • Certificate trust is the most common security failure. If Basic256Sha256 fails, first try no security, then verify the client cert is DER/PEM in the format the package expects and that the server trust list includes it.
  • Pure-PHP performance is fine for telemetry, not for high-frequency control. Batch reads and use subscriptions. Avoid opening a new connection per tag or per request.
  • Do not share one client across forked workers. In queue workers, Octane, or Swoole, create the client after fork and reconnect on worker recycle.
  • Set explicit timeouts. A dead PLC can hang a PHP process on a blocking socket read. Configure connect and request timeouts, and fail fast in web requests.
  • Debug with UaExpert in parallel. If PHP disagrees with the server, confirm the NodeId, namespace, and security settings in UaExpert first. Then enable the package’s verbose logging or inspect the service response objects.
  • Pin the version. This is a small-community package; pin a known-good release in composer.json and add an integration test against a local OPC UA test server (open62541, Prosys, or the package’s own fixtures) so upgrades are validated.
  • Extension points are in transport and security policy classes. If you need custom logging, header injection, or a non-standard transport, subclass or decorate those rather than modifying Client directly.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console