Examples

Complete, copy-paste-ready code examples for all major features.

Read a Single Value

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

$dv = $client->read('i=2259');
echo "ServerState: " . $dv->getValue(); // 0 = Running

$client->disconnect();

Read Multiple Values (Array)

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

$results = $client->readMulti([
    ['nodeId' => 'i=2259'],
    ['nodeId' => 'ns=2;i=1001'],
    ['nodeId' => 'ns=2;s=Temperature'],
]);

foreach ($results as $i => $dv) {
    echo "[$i] {$dv->getValue()}\n";
}

$client->disconnect();

Read Multiple Values (Fluent Builder)

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

$results = $client->readMulti()
    ->node('i=2259')->value()
    ->node('ns=2;i=1001')->displayName()
    ->node('ns=2;s=Temperature')->value()
    ->execute();

foreach ($results as $dv) {
    echo $dv->getValue() . "\n";
}

$client->disconnect();

Write a Value

use PhpOpcua\LaravelOpcua\Facades\Opcua;
use PhpOpcua\Client\Types\BuiltinType;
use PhpOpcua\Client\Types\StatusCode;

$client = Opcua::connect();

$status = $client->write('ns=2;i=1001', 42, BuiltinType::Int32);

if (StatusCode::isGood($status)) {
    echo "Write OK\n";
}

$client->disconnect();

Write Without Explicit Type (Auto-Detection, v4.0+)

When auto_detect_write_type is enabled (the default), you can omit the type parameter. The client reads the node's DataType attribute, caches it, and uses it for the write.

use PhpOpcua\LaravelOpcua\Facades\Opcua;
use PhpOpcua\Client\Types\StatusCode;

$client = Opcua::connect();

// No BuiltinType needed — the client auto-detects that ns=2;i=1001 is Int32
$status = $client->write('ns=2;i=1001', 42);

if (StatusCode::isGood($status)) {
    echo "Write OK (type auto-detected)\n";
}

$client->disconnect();

Write Multiple Values (Fluent Builder)

use PhpOpcua\LaravelOpcua\Facades\Opcua;
use PhpOpcua\Client\Types\StatusCode;

$client = Opcua::connect();

$results = $client->writeMulti()
    ->node('ns=2;i=1001')->int32(42)
    ->node('ns=2;i=1002')->double(3.14)
    ->node('ns=2;s=Label')->string('active')
    ->execute();

foreach ($results as $i => $status) {
    echo "[$i] " . (StatusCode::isGood($status) ? 'OK' : 'FAIL') . "\n";
}

$client->disconnect();

Read with Refresh Parameter (v4.0+)

When read metadata cache is enabled, use the refresh parameter to bypass the cache:

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();
$client->setReadMetadataCache(true);

// Cached read (default)
$dv = $client->read('ns=2;i=1001');
echo "Cached: " . $dv->getValue() . "\n";

// Force fresh read from server
$dv = $client->read('ns=2;i=1001', refresh: true);
echo "Fresh: " . $dv->getValue() . "\n";

$client->disconnect();

Browse the Address Space

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

$refs = $client->browse('i=85');

foreach ($refs as $ref) {
    echo "{$ref->displayName} ({$ref->nodeClass->name}) — {$ref->nodeId}\n";
}

$client->disconnect();

Recursive Browse

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

$tree = $client->browseRecursive('i=85', maxDepth: 3);

function printTree(array $nodes, int $indent = 0): void
{
    foreach ($nodes as $node) {
        echo str_repeat('  ', $indent) . $node->reference->displayName . "\n";
        if (!empty($node->children)) {
            printTree($node->children, $indent + 1);
        }
    }
}

printTree($tree);

$client->disconnect();

Path Resolution

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

$nodeId = $client->resolveNodeId('/Objects/Server/ServerStatus');
$dv = $client->read($nodeId);

echo "ServerStatus NodeId: {$nodeId}\n";
echo "Value: {$dv->getValue()}\n";

$client->disconnect();

Call a Method

use PhpOpcua\LaravelOpcua\Facades\Opcua;
use PhpOpcua\Client\Types\Variant;
use PhpOpcua\Client\Types\BuiltinType;
use PhpOpcua\Client\Types\StatusCode;

$client = Opcua::connect();

$result = $client->call(
    'ns=2;i=100',   // parent object
    'ns=2;i=200',   // method
    [
        new Variant(BuiltinType::Double, 3.0),
        new Variant(BuiltinType::Double, 4.0),
    ],
);

if (StatusCode::isGood($result->statusCode)) {
    echo "Result: {$result->outputArguments[0]->value}\n"; // 7.0
}

$client->disconnect();

Subscribe to Data Changes

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

$sub = $client->createSubscription(publishingInterval: 500.0);

$client->createMonitoredItems($sub->subscriptionId, [
    ['nodeId' => 'ns=2;i=1001', 'clientHandle' => 1],
    ['nodeId' => 'ns=2;i=1002', 'clientHandle' => 2],
]);

// Poll for notifications
for ($i = 0; $i < 10; $i++) {
    $pub = $client->publish();

    foreach ($pub->notifications as $notif) {
        echo "[handle={$notif['clientHandle']}] {$notif['dataValue']->getValue()}\n";
    }

    usleep(500_000);
}

$client->deleteSubscription($sub->subscriptionId);
$client->disconnect();

Historical Data

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

$history = $client->historyReadRaw(
    'ns=2;i=1001',
    new \DateTimeImmutable('-1 hour'),
    new \DateTimeImmutable('now'),
);

foreach ($history as $dv) {
    echo "[{$dv->sourceTimestamp->format('H:i:s')}] {$dv->getValue()}\n";
}

$client->disconnect();

Secure Connection with Authentication

use PhpOpcua\LaravelOpcua\Facades\Opcua;

// Option 1: Via config/opcua.php (recommended)
// 'connections' => [
//     'secure' => [
//         'endpoint'           => 'opc.tcp://10.0.0.10:4840',
//         'security_policy'    => 'Basic256Sha256',
//         'security_mode'      => 'SignAndEncrypt',
//         'username'           => 'operator',
//         'password'           => 'secret',
//         'client_certificate' => '/etc/opcua/certs/client.pem',
//         'client_key'         => '/etc/opcua/certs/client.key',
//     ],
// ],
$client = Opcua::connect('secure');

// Option 2: Via connectTo with inline config
$client = Opcua::connectTo('opc.tcp://10.0.0.10:4840', [
    'security_policy'    => 'Basic256Sha256',
    'security_mode'      => 'SignAndEncrypt',
    'username'           => 'operator',
    'password'           => 'secret',
    'client_certificate' => '/etc/opcua/certs/client.pem',
    'client_key'         => '/etc/opcua/certs/client.key',
]);

$value = $client->read('ns=2;i=1001');
echo $value->getValue();

$client->disconnect();

ECC Secure Connection

use PhpOpcua\LaravelOpcua\Facades\Opcua;

// ECC security — no client certificate needed (auto-generated)
$client = Opcua::connectTo('opc.tcp://10.0.0.10:4840', [
    'security_policy' => 'ECC_nistP256',
    'security_mode'   => 'SignAndEncrypt',
    'username'        => 'operator',
    'password'        => 'secret',
]);

$value = $client->read('ns=2;i=1001');
echo $value->getValue();

$client->disconnect();

ECC disclaimer: ECC security policies (ECC_nistP256, ECC_nistP384, ECC_brainpoolP256r1, ECC_brainpoolP384r1) are fully implemented and tested against the OPC Foundation's UA-.NETStandard reference stack. No commercial OPC UA vendor supports ECC endpoints yet.

Multiple Connections

use PhpOpcua\LaravelOpcua\Facades\Opcua;

// Connect to two PLCs simultaneously
$plc1 = Opcua::connect('plc-line-1');
$plc2 = Opcua::connect('plc-line-2');

$temp1 = $plc1->read('ns=2;s=Temperature')->getValue();
$temp2 = $plc2->read('ns=2;s=Temperature')->getValue();

echo "Line 1: {$temp1}°C\n";
echo "Line 2: {$temp2}°C\n";

Opcua::disconnectAll();

Dependency Injection in a Controller

use PhpOpcua\LaravelOpcua\OpcuaManager;
use Illuminate\Http\JsonResponse;

class PlcController extends Controller
{
    public function status(OpcuaManager $opcua): JsonResponse
    {
        $client = $opcua->connect();
        $state = $client->read('i=2259')->getValue();
        $client->disconnect();

        return response()->json([
            'server_state' => $state,
            'daemon_active' => $opcua->isSessionManagerRunning(),
        ]);
    }

    public function write(OpcuaManager $opcua): JsonResponse
    {
        $client = $opcua->connect();
        // Type is optional in v4 — auto-detected when auto_detect_write_type is enabled
        $status = $client->write('ns=2;i=1001', 42);
        $client->disconnect();

        return response()->json([
            'success' => StatusCode::isGood($status),
        ]);
    }
}

Type Discovery

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

// Discover all custom types
$count = $client->discoverDataTypes();
echo "Discovered {$count} custom types\n";

// Now reading structured types works without manual codecs
$point = $client->read('ns=2;s=MyPoint')->getValue();
// ['x' => 1.5, 'y' => 2.5, 'z' => 3.5]

$client->disconnect();

Error Handling

use PhpOpcua\LaravelOpcua\Facades\Opcua;
use PhpOpcua\Client\Exception\ConnectionException;
use PhpOpcua\Client\Exception\ServiceException;
use PhpOpcua\Client\Types\StatusCode;

try {
    $client = Opcua::connect();

    $dv = $client->read('ns=99;i=99999');
    if (StatusCode::isBad($dv->statusCode)) {
        echo "Bad status: {$dv->statusCode}\n";
    }

    $client->disconnect();
} catch (ConnectionException $e) {
    echo "Connection failed: {$e->getMessage()}\n";
} catch (ServiceException $e) {
    echo "Service error: {$e->getMessage()}\n";
}

Trust Store Configuration (v4.0+)

use PhpOpcua\LaravelOpcua\Facades\Opcua;
use PhpOpcua\Client\Security\TrustPolicy;

$client = Opcua::connection();

// Configure trust store
$client->setTrustStorePath(storage_path('opcua/trust'));
$client->setTrustPolicy(TrustPolicy::FingerprintAndExpiry);
$client->autoAccept(true); // TOFU mode

$client->connect('opc.tcp://192.168.1.100:4840');

$dv = $client->read('i=2259');
echo "ServerState: " . $dv->getValue() . "\n";

$client->disconnect();

Certificate Trust Management (v4.0+)

use PhpOpcua\LaravelOpcua\Facades\Opcua;
use PhpOpcua\Client\Exception\UntrustedCertificateException;

try {
    $client = Opcua::connect();
} catch (UntrustedCertificateException $e) {
    echo "Untrusted certificate: " . $e->getFingerprint() . "\n";

    // Approve the certificate and retry
    $client = Opcua::connection();
    $client->trustCertificate($e->getCertificate());
    $client->connect('opc.tcp://192.168.1.100:4840');
}

// Later, revoke trust
$client->untrustCertificate($derBytes);

$client->disconnect();

Event Dispatcher Setup (v4.0+)

use PhpOpcua\LaravelOpcua\Facades\Opcua;
use PhpOpcua\Client\Events\AfterRead;
use PhpOpcua\Client\Events\Connected;
use Psr\EventDispatcher\EventDispatcherInterface;

$client = Opcua::connect();

// Attach Laravel's event dispatcher
$client->setEventDispatcher(app(EventDispatcherInterface::class));

// Now all OPC UA operations fire PSR-14 events
$dv = $client->read('i=2259'); // fires BeforeRead + AfterRead

$client->disconnect(); // fires Disconnected

Modify Monitored Items (v4.0+)

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

$sub = $client->createSubscription(publishingInterval: 500.0);

$client->createMonitoredItems($sub->subscriptionId, [
    ['nodeId' => 'ns=2;i=1001', 'clientHandle' => 1, 'samplingInterval' => 1000.0],
    ['nodeId' => 'ns=2;i=1002', 'clientHandle' => 2, 'samplingInterval' => 1000.0],
]);

// Later, change the sampling interval for item 1
$client->modifyMonitoredItems($sub->subscriptionId, [
    ['monitoredItemId' => 1, 'samplingInterval' => 200.0],
]);

$client->deleteSubscription($sub->subscriptionId);
$client->disconnect();

Set Triggering (v4.0+)

Link monitored items so that one item triggers reporting of others:

use PhpOpcua\LaravelOpcua\Facades\Opcua;

$client = Opcua::connect();

$sub = $client->createSubscription(publishingInterval: 500.0);

$client->createMonitoredItems($sub->subscriptionId, [
    ['nodeId' => 'ns=2;i=1001', 'clientHandle' => 1],  // triggering item
    ['nodeId' => 'ns=2;i=1002', 'clientHandle' => 2],  // triggered item
    ['nodeId' => 'ns=2;i=1003', 'clientHandle' => 3],  // triggered item
]);

// When item 1 changes, also report items 2 and 3
$client->setTriggering(
    $sub->subscriptionId,
    1,          // triggering monitored item ID
    [2, 3],     // links to add
    [],         // links to remove
);

$client->deleteSubscription($sub->subscriptionId);
$client->disconnect();

Testing with MockClient

use PhpOpcua\Client\Testing\MockClient;
use PhpOpcua\Client\Types\DataValue;
use PhpOpcua\Client\Types\StatusCode;

// In a Pest / PHPUnit test
it('reads temperature from PLC', function () {
    $mock = MockClient::create()
        ->onRead('ns=2;s=Temperature', fn() => DataValue::ofDouble(23.5));

    $value = $mock->read('ns=2;s=Temperature');

    expect($value->getValue())->toBe(23.5);
    expect($value->statusCode)->toBe(StatusCode::Good);
    expect($mock->callCount('read'))->toBe(1);
});