Reading & Writing Values
Getting Started
Introduction ConnectionCore
Browsing Reading-writing Method-call Subscriptions History-readReference
Types Error-handling Security Architecture Extension-object-codecs Testing Events Trust-storeReading & Writing Values
Reading a Value
use PhpOpcua\Client\Types\NodeId;
use PhpOpcua\Client\Types\StatusCode;
// Using string format
$dataValue = $client->read('i=2259'); // ServerStatus_State
// Or with a NodeId object
$dataValue = $client->read(NodeId::numeric(0, 2259));
if (StatusCode::isGood($dataValue->statusCode)) {
echo "Value: " . $dataValue->getValue() . "\n";
}Events: Every
read()dispatches aNodeValueReadevent. See Events.
Metadata Cache
Attributes like DisplayName, BrowseName, DataType, and NodeClass are static — they don't change at runtime. Enable metadata caching to avoid redundant server reads:
// Enable on the builder before connecting
$client = ClientBuilder::create()
->setReadMetadataCache(true)
->connect('opc.tcp://localhost:4840');
// First call: reads from server, caches the result
$name = $client->read('ns=2;i=1001', AttributeId::DisplayName);
// Second call: served from cache (no server round-trip)
$name = $client->read('ns=2;i=1001', AttributeId::DisplayName);
// Force a refresh from the server
$name = $client->read('ns=2;i=1001', AttributeId::DisplayName, refresh: true);Rules:
- Disabled by default — opt-in via
setReadMetadataCache(true). - Value (attribute 13) is never cached — always reads from the server, regardless of the setting.
refresh: truebypasses the cache and re-reads from the server, then updates the cache.- Uses the same PSR-16 cache backend as browse and write type detection.
invalidateCache($nodeId)clears all cached metadata for that node.
Reading a Specific Attribute
By default, read() targets the Value attribute (id 13). You can read any attribute:
use PhpOpcua\Client\Types\AttributeId;
$displayName = $client->read(NodeId::numeric(0, 2259), AttributeId::DisplayName);
$dataType = $client->read(NodeId::numeric(0, 2259), AttributeId::DataType);Common attributes:
| Constant | Value | Description |
|---|---|---|
AttributeId::NodeId |
1 | The node's NodeId |
AttributeId::NodeClass |
2 | Node class |
AttributeId::BrowseName |
3 | Browse name |
AttributeId::DisplayName |
4 | Display name |
AttributeId::Description |
5 | Description |
AttributeId::Value |
13 | The value (default) |
AttributeId::DataType |
14 | Data type NodeId |
AttributeId::AccessLevel |
17 | Access level bitmask |
Reading Multiple Values
// Fluent builder
$results = $client->readMulti()
->node('i=2259')->value()
->node('i=2267')->value()
->node('ns=2;s=Temperature')->value()
->execute();
foreach ($results as $dataValue) {
if (StatusCode::isGood($dataValue->statusCode)) {
echo $dataValue->getValue() . "\n";
}
}
// Or with array (still works)
$results = $client->readMulti([
['nodeId' => 'i=2259'],
['nodeId' => 'i=2267'],
['nodeId' => 'ns=2;s=Temperature', 'attributeId' => AttributeId::Value],
]);Tip: The builder's
->node()adds a node, then you pick the attribute (->value(),->displayName(), etc.). Call->execute()to send the request.
DataValue Properties
$dataValue->getValue(); // mixed -- unwrapped value (extracts from Variant)
$dataValue->variant; // ?Variant -- typed variant
$dataValue->statusCode; // int -- OPC UA status code
$dataValue->sourceTimestamp; // ?DateTimeImmutable
$dataValue->serverTimestamp; // ?DateTimeImmutableWriting a Value
use PhpOpcua\Client\Types\BuiltinType;
$statusCode = $client->write(
'ns=2;i=1234', // or NodeId::numeric(2, 1234)
42,
BuiltinType::Int32
);
if (StatusCode::isGood($statusCode)) {
echo "Write successful\n";
} else {
echo "Write failed: " . StatusCode::getName($statusCode) . "\n";
}
// Events: dispatches NodeValueWritten on success, NodeValueWriteFailed otherwiseAuto-Detect Write Type
By default, the client automatically detects the node's type before writing. You can omit the BuiltinType parameter:
// Auto-detect type (reads the node first, caches the type)
$client->write('ns=2;i=1234', 42);
// Explicit type (validated against the node when auto-detect is on)
$client->write('ns=2;i=1234', 42, BuiltinType::Int32);The detected type is cached (PSR-16) so subsequent writes to the same node skip the read.
Behavior:
| Auto-detect | $type passed |
What happens |
|---|---|---|
| ON (default) | No | Reads node, caches type, writes |
| ON | Yes | Uses the type directly, no read |
| OFF | No | Throws WriteTypeDetectionException |
| OFF | Yes | Uses the type directly, no read |
Disable auto-detect:
$client = ClientBuilder::create()
->setAutoDetectWriteType(false)
->connect('opc.tcp://localhost:4840');Exceptions:
WriteTypeDetectionException— node has no readable value, or auto-detect is off and no type provided
Events:
WriteTypeDetecting— dispatched before the type detection startsWriteTypeDetected— dispatched after the type is determined (with$detectedTypeand$fromCache)
Cache invalidation:
$client->invalidateCache($nodeId); // clears cached write type (and browse cache)
$client->flushCache(); // clears everythingWriting Multiple Values
// Fluent builder — auto-detect type
$results = $client->writeMulti()
->node('ns=2;i=1001')->value(3.14)
->node('ns=2;i=1002')->value('Hello')
->node('ns=2;i=1003')->value(true)
->execute();
// Fluent builder — explicit type
$results = $client->writeMulti()
->node('ns=2;i=1001')->typed(3.14, BuiltinType::Double)
->node('ns=2;i=1002')->typed('Hello', BuiltinType::String)
->node('ns=2;i=1003')->typed(true, BuiltinType::Boolean)
->execute();
foreach ($results as $i => $statusCode) {
echo "Item $i: " . StatusCode::getName($statusCode) . "\n";
}
// Or with array (still works)
$results = $client->writeMulti([
[
'nodeId' => 'ns=2;i=1001',
'value' => 3.14,
'type' => BuiltinType::Double,
],
[
'nodeId' => 'ns=2;i=1002',
'value' => 'Hello',
'type' => BuiltinType::String,
],
[
'nodeId' => 'ns=2;i=1003',
'value' => true,
'type' => BuiltinType::Boolean,
],
]);Tip: The write builder uses
->node()to pick the target, then->value($val, $type)to set what to write. Call->execute()to send.
Writing to a Specific Attribute
By default, write() targets the Value attribute (id 13):
$results = $client->writeMulti([
[
'nodeId' => NodeId::numeric(2, 1001),
'value' => 100,
'type' => BuiltinType::Int32,
'attributeId' => 13,
],
]);Writing Arrays
use PhpOpcua\Client\Types\Variant;
use PhpOpcua\Client\Types\DataValue;
// Using Variant directly
$variant = new Variant(BuiltinType::Int32, [1, 2, 3, 4, 5]);
$dataValue = new DataValue($variant);
// Or through writeMulti
$results = $client->writeMulti([
[
'nodeId' => NodeId::numeric(2, 2001),
'value' => [10, 20, 30],
'type' => BuiltinType::Int32,
],
]);Automatic Batching
OPC UA servers can limit how many nodes you read or write per request. The client handles this transparently.
How It Works
After connect(), the client reads the server's MaxNodesPerRead and MaxNodesPerWrite limits. When readMulti() or writeMulti() exceeds that limit, the request is split automatically and results are merged in order.
$client = ClientBuilder::create()
->connect('opc.tcp://localhost:4840');
// Server says MaxNodesPerRead = 100
// This is split into 10 requests of 100 each
$results = $client->readMulti($items1000);
// $results contains all 1000 DataValues, in orderYou can check the discovered limits:
$client->getServerMaxNodesPerRead(); // e.g. 100, or null
$client->getServerMaxNodesPerWrite(); // e.g. 100, or nullSetting a Manual Batch Size
Override the server limit or set one when the server does not report any:
$client = ClientBuilder::create()
->setBatchSize(50)
->connect('opc.tcp://localhost:4840');Priority order: your setBatchSize(N) (N > 0) beats the server-reported limit, which beats no batching.
Disabling Batching
Skip both batching and the server limits discovery on connect:
$client = ClientBuilder::create()
->setBatchSize(0)
->connect('opc.tcp://localhost:4840');Tip: Use this if you know the server has no limits and want to save the extra read on connect.
Batching Summary
getBatchSize() |
Server reports | Discovery on connect | Effective batch size |
|---|---|---|---|
null (default) |
100 | Yes | 100 |
null (default) |
0 (no limit) | Yes | No batching |
null (default) |
Not supported | Yes | No batching |
50 |
100 | Yes | 50 |
50 |
0 | Yes | 50 |
0 (disabled) |
Any | Skipped | No batching |
Note: Batching only applies to
readMulti()andwriteMulti(). Singleread()andwrite()calls always go as individual requests.
Supported Data Types
| BuiltinType | PHP Type | Example |
|---|---|---|
Boolean |
bool |
true |
SByte |
int |
-128 to 127 |
Byte |
int |
0 to 255 |
Int16 |
int |
-32768 to 32767 |
UInt16 |
int |
0 to 65535 |
Int32 |
int |
-2^31 to 2^31-1 |
UInt32 |
int |
0 to 2^32-1 |
Int64 |
int |
-2^63 to 2^63-1 |
UInt64 |
int |
0 to 2^64-1 |
Float |
float |
3.14 |
Double |
float |
3.141592653589793 |
String |
string |
'Hello' |
DateTime |
DateTimeImmutable |
new DateTimeImmutable() |
Guid |
string |
'550e8400-e29b-41d4-a716-446655440000' |
ByteString |
string |
Binary data |
NodeId |
NodeId |
NodeId::numeric(0, 85) |
QualifiedName |
QualifiedName |
new QualifiedName(0, 'Name') |
LocalizedText |
LocalizedText |
new LocalizedText('en', 'Text') |
Status Codes
use PhpOpcua\Client\Types\StatusCode;
$statusCode = $dataValue->statusCode;
StatusCode::isGood($statusCode); // true if 0x0XXXXXXX
StatusCode::isBad($statusCode); // true if 0x8XXXXXXX
StatusCode::isUncertain($statusCode); // true if 0x4XXXXXXX
StatusCode::getName($statusCode); // e.g. "BadNodeIdUnknown"Common status codes:
| Constant | Value | Meaning |
|---|---|---|
StatusCode::Good |
0x00000000 |
Success |
StatusCode::BadNodeIdUnknown |
0x80340000 |
Node does not exist |
StatusCode::BadTypeMismatch |
0x80740000 |
Value type mismatch |
StatusCode::BadNotWritable |
0x803B0000 |
Node is read-only |
StatusCode::BadNotReadable |
0x803E0000 |
Node is not readable |
StatusCode::BadUserAccessDenied |
0x801F0000 |
Access denied |
StatusCode::BadTimeout |
0x800A0000 |
Operation timed out |