PHP Classes

File: doc/03-browsing.md

Recommend this page to a friend!
  Packages of Gianfrancesco Aurecchia   OPC UA Client   doc/03-browsing.md   Download  
File: doc/03-browsing.md
Role: Auxiliary data
Content type: text/markdown
Description: Auxiliary data
Class: OPC UA Client
Control devices that support the OPC UA protocol
Author: By
Last change:
Date: 16 days ago
Size: 11,391 bytes
 

Contents

Class file image Download

Browsing the Address Space

Browsing a Node

browse() returns the references (children) of a node:

use PhpOpcua\Client\Types\NodeId;

// Using string format
$references = $client->browse('i=85'); // Objects folder

// Or with a NodeId object
$references = $client->browse(NodeId::numeric(0, 85));
// Events: dispatches NodeBrowsed after each browse() call, CacheHit/CacheMiss when cache is active

foreach ($references as $ref) {
    echo sprintf(
        "%s (NodeId: ns=%d;i=%s, Class: %s)\n",
        $ref->displayName,
        $ref->nodeId->namespaceIndex,
        $ref->nodeId->identifier,
        $ref->nodeClass->name,
    );
}

Browse Parameters

use PhpOpcua\Client\Types\BrowseDirection;

use PhpOpcua\Client\Types\NodeClass;

$references = $client->browse(
    nodeId: NodeId::numeric(0, 85),
    direction: BrowseDirection::Forward,
    referenceTypeId: NodeId::numeric(0, 33),  // HierarchicalReferences
    includeSubtypes: true,
    nodeClasses: [NodeClass::Object, NodeClass::Variable],  // filter by class
);

Browse direction:

| Direction | Value | Meaning | |-----------|-------|---------| | Forward | 0 | Children | | Inverse | 1 | Parents | | Both | 2 | Both directions |

Common reference types:

| NodeId | Name | |--------|------| | NodeId::numeric(0, 33) | HierarchicalReferences | | NodeId::numeric(0, 35) | Organizes | | NodeId::numeric(0, 47) | HasComponent | | NodeId::numeric(0, 46) | HasProperty | | NodeId::numeric(0, 40) | HasTypeDefinition |

Node class filter:

Pass an array of NodeClass enum values to filter results. Empty array (default) means all classes.

use PhpOpcua\Client\Types\NodeClass;

// Only objects and variables
$refs = $client->browse($nodeId, nodeClasses: [NodeClass::Object, NodeClass::Variable]);

// Only methods
$refs = $client->browse($nodeId, nodeClasses: [NodeClass::Method]);

// All classes (default)
$refs = $client->browse($nodeId);

Available NodeClass values: Object, Variable, Method, ObjectType, VariableType, ReferenceType, DataType, View.

ReferenceDescription Properties

Each reference returned by browse() has these properties:

$ref->referenceTypeId;   // NodeId
$ref->isForward;         // bool
$ref->nodeId;            // NodeId
$ref->browseName;        // QualifiedName
$ref->displayName;       // LocalizedText
$ref->nodeClass;         // NodeClass enum
$ref->typeDefinition;    // ?NodeId

Handling Continuation

Some servers paginate large result sets. You have two options.

Automatic (recommended)

browseAll() follows all continuation points and returns the complete list:

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

Manual

If you need control over pagination:

$result = $client->browseWithContinuation(NodeId::numeric(0, 85));

$allRefs = $result->references;
$continuationPoint = $result->continuationPoint;

while ($continuationPoint !== null) {
    $next = $client->browseNext($continuationPoint);
    $allRefs = array_merge($allRefs, $next->references);
    $continuationPoint = $next->continuationPoint;
}

Recursive Browse

browseRecursive() walks the address space from a starting node and builds a tree of BrowseNode objects. Continuation points are handled at each level, and cycle detection prevents infinite loops on circular references.

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

foreach ($tree as $node) {
    echo $node->displayName . "\n";

    foreach ($node->getChildren() as $child) {
        echo "  " . $child->displayName . "\n";
    }
}

Parameters

$tree = $client->browseRecursive(
    nodeId: NodeId::numeric(0, 85),
    direction: BrowseDirection::Forward,
    maxDepth: 3,
    referenceTypeId: NodeId::numeric(0, 33),
    includeSubtypes: true,
    nodeClasses: [NodeClass::Object, NodeClass::Variable],
);

| Parameter | Default | Description | |-----------|---------|-------------| | nodeId | (required) | Starting node | | direction | Forward | Browse direction | | maxDepth | null (configured default: 10) | Max recursion depth. -1 for unlimited (capped at 256) | | referenceTypeId | null | Filter by reference type | | includeSubtypes | true | Include reference subtypes | | nodeClasses | [] | Filter by NodeClass enum values. Empty = all classes |

Depth Limits

| maxDepth | Behavior | |------------|----------| | null | Uses configured default (10, or your setDefaultBrowseMaxDepth() value) | | 1 | Direct children only | | -1 | Unlimited (capped at 256) | | > 256 | Capped at 256 |

You can change the default globally:

$client = ClientBuilder::create()
    ->setDefaultBrowseMaxDepth(20)
    ->connect('opc.tcp://localhost:4840');

$tree = $client->browseRecursive($nodeId);              // uses 20
$tree = $client->browseRecursive($nodeId, maxDepth: 3); // override: 3

> Warning: High depth values can cause problems. Each level sends one browse request per node -- thousands of nodes means thousands of round-trips. Large trees eat memory, and massive browsing can overwhelm resource-constrained PLCs. Start small and increase only when needed.

BrowseNode Properties

Each node in the tree wraps a ReferenceDescription and holds its children:

$node->reference;      // ReferenceDescription
$node->nodeId;         // NodeId
$node->displayName;    // LocalizedText
$node->browseName;     // QualifiedName
$node->nodeClass;      // NodeClass enum
$node->getChildren();  // BrowseNode[]
$node->hasChildren();  // bool

Cycle Detection

The method tracks every visited NodeId. If a node appears again, it is included as a leaf with no children, cutting the recursion. This matches the behavior of open62541, node-opcua, and the OPC Foundation .NET SDK.

Printing a Tree

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

$tree = $client->browseRecursive(NodeId::numeric(0, 85), maxDepth: 3);
printTree($tree);

Path Resolution

Instead of browsing step by step, resolve a human-readable path to a NodeId in one call:

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

This calls TranslateBrowsePathsToNodeIds under the hood -- a single round-trip, much faster than manual browsing.

Path Format

  • Segments separated by `/`
  • Leading `/` is optional
  • Default start is Root (`ns=0;i=84`)
  • For non-zero namespaces, use `ns:Name` format
// Simple path
$nodeId = $client->resolveNodeId('/Objects/Server');

// With namespaced segments
$nodeId = $client->resolveNodeId('/Objects/2:MyPLC/2:Temperature');

// Custom starting node
$nodeId = $client->resolveNodeId('Server', NodeId::numeric(0, 85));

Advanced: translateBrowsePaths

For full control over TranslateBrowsePathsToNodeIds, including resolving multiple paths in a single request:

use PhpOpcua\Client\Types\QualifiedName;
use PhpOpcua\Client\Types\StatusCode;

// Fluent builder
$results = $client->translateBrowsePaths()
    ->from('i=85')->path('Server', 'ServerStatus')
    ->execute();

if (StatusCode::isGood($results[0]->statusCode)) {
    $targetNodeId = $results[0]->targets[0]->targetId;
}

// Or with array (still works)
$results = $client->translateBrowsePaths([
    [
        'startingNodeId' => NodeId::numeric(0, 85),
        'relativePath' => [
            ['targetName' => new QualifiedName(0, 'Server')],
            ['targetName' => new QualifiedName(0, 'ServerStatus')],
        ],
    ],
]);

> Tip: The builder's ->from() sets the starting node, and ->path() accepts segment names as separate arguments. Call ->from() again to add another path in the same request.

Each path element supports:

| Field | Default | Description | |-------|---------|-------------| | targetName | (required) | QualifiedName of the target | | referenceTypeId | HierarchicalReferences | Reference type to follow | | isInverse | false | Follow inverse references | | includeSubtypes | true | Include subtypes |

Caching

Browse results are cached by default using an in-memory PSR-16 cache with a 300-second TTL. This avoids redundant server round-trips when the address space is browsed repeatedly ? common in industrial PLC environments where the node tree rarely changes.

Default Behavior

Caching is active out of the box. No setup required:

$refs = $client->browse('i=85');       // hits the server
$refs = $client->browse('i=85');       // served from cache

Cache Bypass

Skip the cache for a single call with useCache: false:

$refs = $client->browse('i=85', useCache: false);
$refs = $client->browseAll('i=85', useCache: false);
$nodeId = $client->resolveNodeId('/Objects/Server', useCache: false);

Custom Cache Driver

Any PSR-16 CacheInterface implementation works ? including Laravel's cache:

use PhpOpcua\Client\ClientBuilder;
use PhpOpcua\Client\Cache\InMemoryCache;
use PhpOpcua\Client\Cache\FileCache;

// In-memory (default)
$client = ClientBuilder::create()
    ->setCache(new InMemoryCache(ttl: 300))
    ->connect('opc.tcp://localhost:4840');

// File-based (survives PHP process restart)
$client = ClientBuilder::create()
    ->setCache(new FileCache('/tmp/opcua-cache', ttl: 600))
    ->connect('opc.tcp://localhost:4840');

// Laravel
$client = ClientBuilder::create()
    ->setCache(app('cache')->store('redis'))
    ->connect('opc.tcp://localhost:4840');

// Disable caching entirely
$client = ClientBuilder::create()
    ->setCache(null)
    ->connect('opc.tcp://localhost:4840');

Cache Invalidation

// Invalidate results for a specific node
$client->invalidateCache(NodeId::numeric(0, 85));

// Flush all cached results
$client->flushCache();

Cached Methods

| Method | Cached | |--------|--------| | browse() | Yes | | browseAll() | Yes | | resolveNodeId() | Yes | | getEndpoints() | Yes | | discoverDataTypes() | Yes (discovered type definitions are cached and replayed) | | browseWithContinuation() | No | | browseNext() | No | | browseRecursive() | No (but each internal browseAll() call is cached) |

Cache Key Format

Keys include the endpoint URL hash, operation type, NodeId, and browse parameters. Two clients pointing at different servers never collide:

opcua:{endpoint_hash}:browse:{nodeId}:{direction}:{includeSubtypes}:{nodeClassMask}
opcua:{endpoint_hash}:browseAll:{nodeId}:{direction}:{includeSubtypes}:{nodeClassMask}
opcua:{endpoint_hash}:resolve:{startingNodeId}:{path_hash}
opcua:{endpoint_hash}:endpoints:{url_hash}
opcua:{endpoint_hash}:dataTypes:{namespaceIndex|all}

Well-Known NodeIds

| Name | NodeId | Description | |------|--------|-------------| | Root | NodeId::numeric(0, 84) | Root of the address space | | Objects | NodeId::numeric(0, 85) | Objects folder | | Types | NodeId::numeric(0, 86) | Types folder | | Views | NodeId::numeric(0, 87) | Views folder | | Server | NodeId::numeric(0, 2253) | Server object | | ServerStatus | NodeId::numeric(0, 2256) | Server status | | ServiceLevel | NodeId::numeric(0, 2267) | Service level |