symfony-opcua · master
Docs · Using the client

Using builders

Fluent builders for read, write, browse, methods, subscriptions, translateBrowsePaths. The same upstream API, used through the autowired Symfony service.

opcua-client exposes fluent builders for the major operations. The Symfony bundle adds nothing on top — you call the same builders through the autowired client.

How builders are obtained

There is no readBuilder() / writeBuilder() / browseBuilder() / callBuilder() / historyBuilder() method. Instead, several of the multi-operation methods accept ?array $items:

  • Called with an array they execute one round-trip and return the result array.
  • Called with null (or no args) they return the fluent builder.
Method Return when null Return when called with args
readMulti(?array $items = null) ReadMultiBuilder DataValue[]
writeMulti(?array $items = null) WriteMultiBuilder int[] (status codes)
translateBrowsePaths(?array $paths) BrowsePathsBuilder BrowsePathResult[]
createMonitoredItems(int $sid, ?array $items = null) MonitoredItemsBuilder MonitoredItemCreateResult[]

The history API has no builder — it's three flat methods (historyReadRaw, historyReadProcessed, historyReadAtTime). See Operations · History.

The single-node convenience methods (read, write, browse, call) are not builders — they round-trip immediately.

Read — readMulti(null) builder

php batch read
use PhpOpcua\Client\Types\AttributeId;

$client = $this->opcua->connect();

// One-shot single read
$dv = $client->read('ns=2;s=Speed');

// Batch — many tags, one round-trip
$values = $client->readMulti(null)
    ->node('ns=2;s=Speed')
    ->node('ns=2;s=Temperature')
    ->node('ns=2;s=Pressure')
    ->execute();

// Non-Value attribute
$displayName = $client->read('ns=2;s=Speed', AttributeId::DisplayName)
    ->getValue();

execute() returns an array of DataValue in the order of nodes added. See Operations · Reading.

Write — writeMulti(null) builder

php batch write
use PhpOpcua\Client\Types\BuiltinType;

$client = $this->opcua->connect();

// Batch
$statuses = $client->writeMulti(null)
    ->node('ns=2;s=Setpoint')->typed(75.0, BuiltinType::Float)
    ->node('ns=2;s=Mode')->value('Auto')
    ->node('ns=2;s=Run')->value(true)
    ->execute();

The builder is "stage-based": call ->node(...) first, then ->value(...) (auto-detect) or ->typed(..., BuiltinType::...) (explicit). See Operations · Writing for the auto-detection rules.

Browse — browse() is one-shot

browse() runs immediately:

php browse
use PhpOpcua\Client\Types\BrowseDirection;
use PhpOpcua\Client\Types\NodeClass;

$client = $this->opcua->connect();

$nodes = $client->browse(
    'ns=2;s=Folder',
    BrowseDirection::Forward,
    true,                                 // includeSubtypes
    NodeClass::Variable->value,           // nodeClassMask
);

$tree = $client->browseRecursive(
    'ns=4;s=Tags',
    BrowseDirection::Forward,
    maxDepth: 5,
);

See Operations · Browsing.

Translate browse paths — translateBrowsePaths(null) builder

php translate paths
$results = $client->translateBrowsePaths(null)
    ->from('ns=2;s=Folder')
    ->path('/Subfolder/Speed')
    ->from('ns=2;s=Folder')
    ->path('/Subfolder/Temperature')
    ->execute();

Returns BrowsePathResult[]not a list of strings.

Method call — call() is one-shot

use PhpOpcua\Client\Types\CallResult;

$result = $client->call(
    'ns=2;s=Recipe',                 // object
    'ns=2;s=Recipe.Load',            // method
    ['NewRecipe', 42],               // input arguments
);

// $result is a CallResult: $result->statusCode (int), $result->outputArguments (array)

There is no callBuilder() and no callMethod(). The return value is a CallResult object — not a [int, array] tuple. See Operations · Method calls.

Subscription — flat API

The subscription API is not a fluent builder:

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

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

// In direct mode: drive the publish loop
while (true) { $client->publish(); }

In managed mode with auto_publish: true, the daemon drives the loop and notifications arrive on the EventDispatcher as DataChangeReceived events — see Session manager · Auto-publish and Events · Data events.

createMonitoredItems(null) returns a MonitoredItemsBuilder for ergonomic item construction.

Chaining with connect()

Every builder is rooted in a connection:

php connect + builder
$values = $this->opcua
    ->connect('historian')
    ->readMulti(null)
    ->node('ns=4;s=Tag1')
    ->node('ns=4;s=Tag2')
    ->execute();

Reads left-to-right: pick connection → pick builder → set parameters → execute.

When to use the builder vs the one-shot

Operation One-shot Use the builder when…
Read one Value $client->read(...) Non-Value attribute, batch
Write one Value $client->write(...) Explicit type, batch
Browse one folder $client->browse(...) (no builder — pass args directly)
Method call $client->call(...) (no builder)
History $client->historyReadRaw(...) (no builder — three flat methods)

Builders accumulate state — don't reuse

Each builder is a fresh instance. Don't reuse a configured builder across iterations:

php don't reuse
// OK — single batch:
$builder = $client->readMulti(null);
foreach ($tags as $node) {
    $builder->node($node);
}
$values = $builder->execute();

// NOT this — second iteration leaks the first's state:
$builder = $client->readMulti(null);
foreach ($groups as $group) {
    foreach ($group as $node) {
        $builder->node($node);
    }
    $values = $builder->execute();  // accumulates across groups!
}

Call the *Multi(null) accessor again for each fresh batch.

Static analysis

PHPStan / Psalm resolve builder return types from the fluent setters. $client->readMulti(null)->node('...')->execute() is DataValue[]. No annotations needed.

Async / queued builders

There's no async builder. PHP-OPC-UA is synchronous. To run an operation off the request thread, dispatch a Messenger message that uses the builder inside the handler:

php queued read
#[AsMessageHandler]
final class SampleBatchHandler
{
    public function __construct(private OpcUaClientInterface $client) {}

    public function __invoke(SampleBatch $message): void
    {
        $builder = $this->client->readMulti(null);
        foreach ($message->nodeIds as $node) {
            $builder->node($node);
        }
        $values = $builder->execute();
        // ...
    }
}

See Integrations · Messenger.

Reference

The builder classes themselves are documented in the upstream opcua-client docs. The Symfony bundle adds nothing — the API is the same.

You've finished Using the client. Continue with Operations · Reading for per-operation detail.

Documentation