PHP Classes

File: llms-full.txt

Recommend this page to a friend!
  Packages of Gianfrancesco Aurecchia   OPC UA Client   llms-full.txt   Download  
File: llms-full.txt
Role: Documentation
Content type: text/plain
Description: Documentation
Class: OPC UA Client
Control devices that support the OPC UA protocol
Author: By
Last change:
Date: 19 days ago
Size: 46,252 bytes
 

Contents

Class file image Download
# OPC UA PHP Client ? Full Documentation > Pure PHP OPC UA client library. Communicates directly over TCP using the OPC UA binary protocol. No external C/C++ dependencies ? only ext-openssl required. All types use public readonly properties. Package: php-opcua/opcua-client Repository: https://github.com/php-opcua/opcua-client Packagist: https://packagist.org/packages/php-opcua/opcua-client License: MIT PHP: >= 8.2 Dependencies: ext-openssl, psr/log ^3.0, psr/simple-cache ^3.0, psr/event-dispatcher ^1.0 (all interface-only, zero runtime code) --- ## 1. Introduction `php-opcua/opcua-client` is an OPC UA client written entirely in PHP. It speaks the OPC UA binary protocol over TCP, handles secure channels, sessions, and crypto ? all without external C/C++ extensions. ### Installation ``` composer require php-opcua/opcua-client ``` ### Features - Binary Protocol ? full OPC UA binary encoding/decoding over TCP - Human-readable NodeId strings ? all methods accept `'i=2259'` or `'ns=2;s=MyNode'` in addition to NodeId objects; invalid strings throw InvalidNodeIdException - Browse ? navigate the server address space, recursive browsing with automatic continuation - Path Resolution ? resolve paths like /Objects/MyPLC/Temperature to NodeIds - Read/Write ? single and multi operations with all OPC UA data types, automatic write type detection (read-before-write) with PSR-16 caching and type mismatch validation, configurable via setAutoDetectWriteType() - Server BuildInfo ? getServerBuildInfo() returns BuildInfo DTO (productName, manufacturerName, softwareVersion, buildNumber, buildDate) in a single readMulti(). Individual methods: getServerProductName(), getServerManufacturerName(), getServerSoftwareVersion(), getServerBuildNumber(), getServerBuildDate() - Node Management ? addNodes(), deleteNodes(), addReferences(), deleteReferences() for dynamic address space modification. addNodes() supports all 8 node classes with automatic ExtensionObject attribute encoding, returns AddNodesResult[]. Other methods return int[] status codes. - Method Call ? invoke OPC UA methods, returns CallResult DTO - Subscriptions ? data change and event monitoring, returns typed DTOs - History Read ? raw, processed, and at-time historical queries - Security ? 10 policies: 6 RSA (None through Aes256Sha256RsaPss) + 4 ECC (EccNistP256, EccNistP384, EccBrainpoolP256r1, EccBrainpoolP384r1), 3 modes - Authentication ? anonymous, username/password, X.509 certificate - PSR-3 Logging ? optional structured logging via any PSR-3 logger; NullLogger by default - PSR-16 Cache ? browse/browseAll/resolveNodeId/getEndpoints/discoverDataTypes results cached by default (InMemoryCache, 300s TTL). Any PSR-16 driver works (FileCache, Laravel, Redis). Per-call bypass with useCache: false. discoverDataTypes replays cached type definitions without server round-trips. Metadata read cache opt-in via setReadMetadataCache(true): caches non-Value attributes (DisplayName, BrowseName, DataType, NodeClass, etc.). Value never cached. read($nodeId, $attr, refresh: true) to bypass. - PSR-14 Events ? 47 granular events at lifecycle points (connection, session, subscription, data change, alarms, read/write, browse, cache, retry, trust store). NullEventDispatcher by default (zero overhead). Alarm events auto-deduced from notification fields. All events carry a $client reference. - Server Trust Store ? persistent server certificate validation via FileTrustStore. Three policies (Fingerprint, FingerprintAndExpiry, Full). TOFU auto-accept. setTrustPolicy(null) disables (default). 5 trust-specific events. CLI trust/trust:list/trust:remove commands. - Auto-Retry ? automatic reconnect on failure - Fluent Builder API ? readMulti(), writeMulti(), createMonitoredItems(), and translateBrowsePaths() support a chainable builder when called without arguments - Auto-Batching ? transparent batching for readMulti/writeMulti - ExtensionObject Codecs ? per-client instance-level codec registry - Automatic DataType Discovery ? discoverDataTypes() auto-detects custom structures from server metadata (OPC UA 1.04+) - Typed everywhere ? all responses return public readonly DTOs, not arrays - MockClient ? in-memory test double implementing OpcUaClientInterface; register handlers, assert calls, no TCP connection - DataValue factory methods ? ofInt32(), ofDouble(), ofString(), ofBoolean(), of($value, BuiltinType), bad(StatusCode), etc. - 1300+ tests (1040+ unit, 250+ integration), 99%+ code coverage on PHP 8.2/8.3/8.4/8.5 ### Property access style All Type classes and result DTOs use public readonly properties: - $ref->nodeId instead of $ref->getNodeId() - $dv->statusCode instead of $dv->getStatusCode() - $result->subscriptionId instead of $result['subscriptionId'] Old getter methods are deprecated but still work. ### Architecture ``` ClientBuilder (entry point, config traits in ClientBuilder/, addModule/replaceModule) +-- connect() ? boots Kernel ? registers Modules ? returns Client Client (proxy to modules, implements OpcUaClientInterface, hasMethod, hasModule) +-- __call() for custom module methods Kernel/ClientKernel (shared infrastructure for all modules) +-- Transport/TcpTransport (TCP socket communication) +-- Protocol/SessionService (session management, kernel-level) +-- Encoding/BinaryEncoder (binary serialization) +-- Encoding/BinaryDecoder (binary deserialization) +-- Security/SecureChannel (message-level security) +-- Security/MessageSecurity (crypto operations) +-- Security/CertificateManager (certificate handling) Kernel/ModuleRegistry (module lifecycle, topological dependency sort, method registry) Module/* (8 built-in service modules, each self-contained) +-- Module/ReadWrite/ (read, write, call + CallResult) +-- Module/Browse/ (browse, getEndpoints + BrowseResultSet) +-- Module/Subscription/ (subscriptions, monitoring + SubscriptionResult, etc.) +-- Module/History/ (history read) +-- Module/NodeManagement/ (add/delete nodes + AddNodesResult) +-- Module/TranslateBrowsePath/ (path resolution + BrowsePathResult) +-- Module/ServerInfo/ (server build info + BuildInfo) +-- Module/TypeDiscovery/ (auto type discovery) Types/* (shared types: NodeId, DataValue, Variant, etc.) Repository/* (per-client codec registry) Exception/* (error hierarchy) ``` ### Module System The Client uses a modular architecture. Each OPC UA service set is a self-contained `ServiceModule` with its own protocol services, DTOs, and method implementations. **Boot flow:** ClientBuilder::connect() creates ClientKernel, creates ModuleRegistry, registers all modules (8 built-in + custom), resolves dependencies with topological sort, boots all modules in order, returns Client with method handlers populated. **Extending:** `ClientBuilder::addModule(new MyModule())` adds a custom module. `ClientBuilder::replaceModule(ReadWriteModule::class, new MyReadWrite())` swaps a built-in. Custom module methods are accessible via `__call()`. Built-in methods are concrete typed one-liners on Client that delegate to handlers. **Introspection:** `$client->hasMethod('read')` and `$client->hasModule(ReadWriteModule::class)` for runtime checks. **Dependencies:** Modules declare dependencies via `requires()`. The registry resolves the graph and throws `MissingModuleDependencyException` for unsatisfied deps. `ModuleConflictException` is thrown if two modules register the same method name. ### Quick Start ```php use PhpOpcua\Client\ClientBuilder; use PhpOpcua\Client\Types\NodeId; $client = ClientBuilder::create() ->connect('opc.tcp://localhost:4840'); // String format ? all methods accept NodeId|string $dv = $client->read('i=2259'); echo $dv->getValue(); // unwrapped value echo $dv->statusCode; // 0 (Good) echo $dv->sourceTimestamp; // DateTimeImmutable // NodeId objects still work $dv = $client->read(NodeId::numeric(0, 2259)); $refs = $client->browse('i=85'); foreach ($refs as $ref) { echo "{$ref->displayName} ({$ref->nodeId})\n"; } $client->disconnect(); ``` --- ## 2. Connection & Configuration ### Basic Connection ```php $client = ClientBuilder::create() ->connect('opc.tcp://localhost:4840'); // ... do stuff ... $client->disconnect(); ``` ### Timeout ```php $builder = ClientBuilder::create(); $builder->setTimeout(10.0); // 10 seconds (default: 5) $client = $builder->connect('opc.tcp://localhost:4840'); ``` ### Connection State States: Disconnected, Connected, Broken ```php $client->getConnectionState(); // ConnectionState enum $client->isConnected(); // bool $client->reconnect(); // re-establishes using last URL ``` ### Auto-Retry ```php $builder = ClientBuilder::create(); $builder->setAutoRetry(3); // retry up to 3 times on connection failure ``` ### Security Configuration ```php use PhpOpcua\Client\ClientBuilder; use PhpOpcua\Client\Security\SecurityPolicy; use PhpOpcua\Client\Security\SecurityMode; $client = ClientBuilder::create() ->setSecurityPolicy(SecurityPolicy::Basic256Sha256) ->setSecurityMode(SecurityMode::SignAndEncrypt) ->setClientCertificate('/certs/client.pem', '/certs/client.key', '/certs/ca.pem') ->connect('opc.tcp://localhost:4840'); ``` RSA Policies: None, Basic128Rsa15, Basic256, Basic256Sha256, Aes128Sha256RsaOaep, Aes256Sha256RsaPss ECC Policies: EccNistP256, EccNistP384, EccBrainpoolP256r1, EccBrainpoolP384r1 Modes: None (1), Sign (2), SignAndEncrypt (3) If no certificate provided, one gets auto-generated in memory (RSA 2048 for RSA policies, ECC matching curve for ECC policies). ECC uses ECDH key agreement (no RSA encryption), HKDF key derivation, and EccEncryptedSecret for password authentication. ### Authentication ```php $builder = ClientBuilder::create(); $builder->setUserCredentials('user', 'password'); // username/password $builder->setUserCertificate('/certs/user.pem', '/certs/user.key'); // X.509 // or nothing ? anonymous by default ``` ### Logging The builder accepts any PSR-3 logger. Without one, a NullLogger is used (zero overhead). ```php use PhpOpcua\Client\ClientBuilder; use Monolog\Logger; use Monolog\Handler\StreamHandler; $logger = new Logger('opcua'); $logger->pushHandler(new StreamHandler('php://stderr', Logger::DEBUG)); $client = ClientBuilder::create(logger: $logger) ->connect('opc.tcp://localhost:4840'); // or $builder = ClientBuilder::create(); $builder->setLogger($logger); ``` Laravel integration: ```php $client = ClientBuilder::create(logger: app('log')) ->connect('opc.tcp://localhost:4840'); ``` ### Events (PSR-14) ```php use Psr\EventDispatcher\EventDispatcherInterface; $builder = ClientBuilder::create(); $builder->setEventDispatcher($yourDispatcher); // or in Laravel: $builder->setEventDispatcher(app(EventDispatcherInterface::class)); ``` NullEventDispatcher by default (zero overhead). 47 events covering connection, session, subscription, data change, alarms, read/write, write type detection, browse, cache, retry, trust store. See section 14 for the full list. Log levels: DEBUG (handshake, secure channel, session), INFO (connect/disconnect, batch splits), WARNING (retries, server limits), ERROR (connection failures). ### Endpoint Discovery ```php $endpoints = $client->getEndpoints('opc.tcp://localhost:4840'); foreach ($endpoints as $ep) { echo "{$ep->endpointUrl} ? {$ep->securityPolicyUri} (mode: {$ep->securityMode})\n"; foreach ($ep->userIdentityTokens as $token) { echo " Auth: {$token->policyId} (type={$token->tokenType})\n"; } } ``` --- ## 3. Browsing the Address Space ### Basic Browse ```php $refs = $client->browse('i=85'); // Objects folder foreach ($refs as $ref) { echo "{$ref->displayName} (ns={$ref->nodeId->namespaceIndex};i={$ref->nodeId->identifier})\n"; } ``` ### Browse All (automatic continuation) ```php $refs = $client->browseAll('i=85'); ``` ### Browse with Continuation (manual) ```php $result = $client->browseWithContinuation('i=85'); $allRefs = $result->references; while ($result->continuationPoint !== null) { $result = $client->browseNext($result->continuationPoint); array_push($allRefs, ...$result->references); } ``` ### ReferenceDescription Properties ```php $ref->referenceTypeId // NodeId $ref->isForward // bool $ref->nodeId // NodeId $ref->browseName // QualifiedName $ref->displayName // LocalizedText $ref->nodeClass // NodeClass enum $ref->typeDefinition // ?NodeId ``` ### Recursive Browse ```php $tree = $client->browseRecursive('i=85', maxDepth: 3); foreach ($tree as $node) { echo "{$node->reference->displayName}\n"; foreach ($node->getChildren() as $child) { echo " {$child->reference->displayName}\n"; } } ``` ### Path Resolution ```php $nodeId = $client->resolveNodeId('/Objects/Server/ServerStatus/State'); $value = $client->read($nodeId); ``` ### translateBrowsePaths (Advanced) ```php // Fluent builder $results = $client->translateBrowsePaths() ->from('i=85')->path('Server', 'ServerStatus') ->execute(); $nodeId = $results[0]->targets[0]->targetId; // NodeId // Or with array (still works) $results = $client->translateBrowsePaths([ [ 'startingNodeId' => NodeId::numeric(0, 85), 'relativePath' => [ ['targetName' => new QualifiedName(0, 'Server')], ], ], ]); ``` ### Caching Browse, browseAll, and resolveNodeId results are cached by default (InMemoryCache, 300s TTL). ```php use PhpOpcua\Client\Cache\InMemoryCache; use PhpOpcua\Client\Cache\FileCache; // Default: InMemoryCache with 300s TTL (active out of the box) // File-based cache (on the builder, before connect) $builder = ClientBuilder::create(); $builder->setCache(new FileCache('/tmp/opcua-cache', defaultTtl: 600)); // Laravel $builder->setCache(app('cache')->store('redis')); // Disable $builder->setCache(null); // Bypass cache for a single call $refs = $client->browse('i=85', useCache: false); $refs = $client->browseAll('i=85', useCache: false); $nodeId = $client->resolveNodeId('/Objects/Server', useCache: false); // Invalidation $client->invalidateCache(NodeId::numeric(0, 85)); $client->flushCache(); ``` --- ## 4. Reading & Writing Values ### Server BuildInfo ```php // All at once ? single readMulti() call, returns BuildInfo DTO $info = $client->getServerBuildInfo(); echo $info->productName; // ?string (ns=0;i=2262) echo $info->manufacturerName; // ?string (ns=0;i=2263) echo $info->softwareVersion; // ?string (ns=0;i=2264) echo $info->buildNumber; // ?string (ns=0;i=2265) echo $info->buildDate; // ?DateTimeImmutable (ns=0;i=2266) // Individual fields $client->getServerProductName(); // ?string $client->getServerManufacturerName(); // ?string $client->getServerSoftwareVersion(); // ?string $client->getServerBuildNumber(); // ?string $client->getServerBuildDate(); // ?DateTimeImmutable ``` ### Read Single Value ```php $dv = $client->read('i=2259'); // or NodeId::numeric(0, 2259) echo $dv->getValue(); // unwrapped value (via Variant) echo $dv->statusCode; // int echo $dv->sourceTimestamp; // ?DateTimeImmutable echo $dv->serverTimestamp; // ?DateTimeImmutable ``` ### Read Multiple Values ```php // Fluent builder $results = $client->readMulti() ->node('i=2259')->value() ->node('i=2267')->value() ->execute(); // Or with array (still works) $results = $client->readMulti([ ['nodeId' => 'i=2259'], ['nodeId' => 'i=2267'], ]); ``` ### Write ```php use PhpOpcua\Client\Types\BuiltinType; $statusCode = $client->write('ns=2;i=1234', 42, BuiltinType::Int32); ``` ### Auto-Batching After connect(), the client reads server limits (MaxNodesPerRead/Write). When readMulti/writeMulti exceeds the limit, requests are split automatically. ```php $builder = ClientBuilder::create(); $builder->setBatchSize(50); // manual override $builder->setBatchSize(0); // disable batching ``` ### Status Codes ```php StatusCode::isGood($code); // true if 0x0XXXXXXX StatusCode::isBad($code); // true if 0x8XXXXXXX StatusCode::getName($code); // e.g. "BadNodeIdUnknown" ``` ### Node Management For servers that support dynamic address space modification: ```php use PhpOpcua\Client\Types\NodeClass; use PhpOpcua\Client\Types\QualifiedName; use PhpOpcua\Client\Types\NodeId; // Add nodes (supports all 8 node classes) $results = $client->addNodes([ [ 'parentNodeId' => 'i=85', 'referenceTypeId' => 'i=35', 'requestedNewNodeId' => 'ns=2;s=MyVar', 'browseName' => new QualifiedName(2, 'MyVar'), 'nodeClass' => NodeClass::Variable, 'typeDefinition' => 'i=63', 'dataType' => NodeId::numeric(0, 6), // Int32 ], ]); // $results[0]->statusCode, $results[0]->addedNodeId // Delete nodes $statusCodes = $client->deleteNodes([ ['nodeId' => 'ns=2;s=MyVar', 'deleteTargetReferences' => true], ]); // Add/delete references $statusCodes = $client->addReferences([ ['sourceNodeId' => 'ns=2;s=A', 'referenceTypeId' => 'i=35', 'isForward' => true, 'targetNodeId' => 'ns=2;s=B', 'targetNodeClass' => NodeClass::Variable], ]); $statusCodes = $client->deleteReferences([ ['sourceNodeId' => 'ns=2;s=A', 'referenceTypeId' => 'i=35', 'isForward' => true, 'targetNodeId' => 'ns=2;s=B', 'deleteBidirectional' => true], ]); ``` AddNodesResult: public readonly statusCode (int), addedNodeId (NodeId). --- ## 5. Method Calling ```php use PhpOpcua\Client\Types\Variant; $result = $client->call( 'i=2253', // or NodeId::numeric(0, 2253) 'i=11492', [new Variant(BuiltinType::UInt32, 1)] ); $result->statusCode; // int $result->inputArgumentResults; // int[] $result->outputArguments; // Variant[] echo $result->outputArguments[0]->value; ``` --- ## 6. Subscriptions & Monitoring ### Create Subscription ```php $sub = $client->createSubscription(publishingInterval: 1000.0); $subscriptionId = $sub->subscriptionId; ``` ### Data Change Monitoring ```php // Fluent builder $results = $client->createMonitoredItems($subscriptionId) ->add('i=2258')->samplingInterval(500.0)->queueSize(10) ->add('ns=2;i=1001') ->execute(); // Or with array (still works) $results = $client->createMonitoredItems($subscriptionId, [ ['nodeId' => 'i=2258', 'samplingInterval' => 500.0, 'queueSize' => 10], ['nodeId' => 'ns=2;i=1001'], ]); ``` ### Event Monitoring ```php $result = $client->createEventMonitoredItem( $subscriptionId, NodeId::numeric(0, 2253), ['EventId', 'EventType', 'SourceName', 'Time', 'Message', 'Severity'], ); ``` ### Receiving Notifications ```php $response = $client->publish(); echo $response->subscriptionId; echo $response->sequenceNumber; echo $response->moreNotifications; foreach ($response->notifications as $notif) { if ($notif['type'] === 'DataChange') { echo $notif['dataValue']->getValue(); } elseif ($notif['type'] === 'Event') { foreach ($notif['eventFields'] as $field) { echo $field->value; } } } ``` ### Transfer & Recovery Transfer subscriptions to a new session and re-request unacknowledged notifications. Primarily used by the session manager package. ```php // Transfer subscriptions from a previous session $results = $client->transferSubscriptions([1, 2, 3], sendInitialValues: true); foreach ($results as $result) { // $result->statusCode, $result->availableSequenceNumbers } // Re-request an unacknowledged notification $notifications = $client->republish(subscriptionId: 1, retransmitSequenceNumber: 42); ``` TransferResult DTO: statusCode (int), availableSequenceNumbers (int[]) ### Cleanup ```php $client->deleteMonitoredItems($subscriptionId, [$monitoredItemId]); $client->deleteSubscription($subscriptionId); ``` --- ## 7. History Read ### Raw History ```php $values = $client->historyReadRaw( 'ns=2;i=1001', startTime: new \DateTimeImmutable('-1 hour'), endTime: new \DateTimeImmutable(), numValuesPerNode: 100, ); foreach ($values as $dv) { echo "[{$dv->sourceTimestamp->format('H:i:s')}] {$dv->getValue()}\n"; } ``` ### Processed History ```php $values = $client->historyReadProcessed( 'ns=2;i=1001', $startTime, $endTime, processingInterval: 3600000.0, aggregateType: 'i=2342', // Average ); ``` Aggregates: Average (2342), Interpolative (2341), Minimum (2346), Maximum (2347), Count (2352), Total (2344) ### History At Time ```php $values = $client->historyReadAtTime('ns=2;i=1001', $timestamps); ``` --- ## 8. Types Reference ### NodeId Properties: namespaceIndex (int), identifier (int|string), type (string) Methods: isNumeric(), isString(), isGuid(), isOpaque(), getEncodingByte(), parse(), toString(), __toString() All client methods accept `NodeId|string`. Strings like `'i=2259'`, `'ns=2;i=1001'`, `'ns=2;s=MyNode'` are parsed automatically. Invalid strings throw `InvalidNodeIdException`. ### Variant Properties: type (BuiltinType), value (mixed), dimensions (?array) Methods: isMultiDimensional() ### DataValue Properties: statusCode (int), sourceTimestamp (?DateTimeImmutable), serverTimestamp (?DateTimeImmutable) Methods: getValue() (unwraps Variant), getVariant() Factory methods: ofInt32(int), ofDouble(float), ofString(string), ofBoolean(bool), ofFloat(float), ofUInt32(int), ofInt16(int), ofUInt16(int), ofInt64(int), ofUInt64(int), ofDateTime(DateTimeImmutable), of(mixed, BuiltinType), bad(int statusCode) ### QualifiedName Properties: namespaceIndex (int), name (string) Methods: __toString() ### LocalizedText Properties: locale (?string), text (?string) Methods: __toString() ### ReferenceDescription Properties: referenceTypeId (NodeId), isForward (bool), nodeId (NodeId), browseName (QualifiedName), displayName (LocalizedText), nodeClass (NodeClass), typeDefinition (?NodeId) ### EndpointDescription Properties: endpointUrl (string), serverCertificate (?string), securityMode (int), securityPolicyUri (string), userIdentityTokens (array), transportProfileUri (string), securityLevel (int) ### UserTokenPolicy Properties: policyId (?string), tokenType (int), issuedTokenType (?string), issuerEndpointUrl (?string), securityPolicyUri (?string) ### BrowseNode Properties: reference (ReferenceDescription) Methods: getChildren(), hasChildren(), addChild() ### BuiltinType (Enum) Boolean(1), SByte(2), Byte(3), Int16(4), UInt16(5), Int32(6), UInt32(7), Int64(8), UInt64(9), Float(10), Double(11), String(12), DateTime(13), Guid(14), ByteString(15), XmlElement(16), NodeId(17), ExpandedNodeId(18), StatusCode(19), QualifiedName(20), LocalizedText(21), ExtensionObject(22), DataValue(23), Variant(24), DiagnosticInfo(25) ### NodeClass (Enum) Unspecified(0), Object(1), Variable(2), Method(4), ObjectType(8), VariableType(16), ReferenceType(32), DataType(64), View(128) ### BrowseDirection (Enum) Forward(0), Inverse(1), Both(2) ### ConnectionState (Enum) Disconnected, Connected, Broken ### StatusCode constants Good (0x00000000), BadUnexpectedError (0x80010000), BadTimeout (0x800A0000), BadNodeIdUnknown (0x80340000), BadNotWritable (0x803B0000), BadNotReadable (0x803E0000), BadTypeMismatch (0x80740000), BadUserAccessDenied (0x801F0000), BadMethodInvalid (0x80750000), BadNoData (0x80B10000) ### AttributeId constants NodeId(1), NodeClass(2), BrowseName(3), DisplayName(4), Description(5), Value(13), DataType(14), AccessLevel(17) ### Result DTOs (all public readonly) Note: Module-specific DTOs have moved from Types\ to their module namespace in v5.0.0. Shared types remain in Types\. Module\Browse\BrowseResultSet: references (ReferenceDescription[]), continuationPoint (?string) Module\TranslateBrowsePath\BrowsePathResult: statusCode (int), targets (BrowsePathTarget[]) Types\BrowsePathTarget: targetId (NodeId), remainingPathIndex (int) Module\ReadWrite\CallResult: statusCode (int), inputArgumentResults (int[]), outputArguments (Variant[]) Module\Subscription\SubscriptionResult: subscriptionId (int), revisedPublishingInterval (float), revisedLifetimeCount (int), revisedMaxKeepAliveCount (int) Module\Subscription\MonitoredItemResult: statusCode (int), monitoredItemId (int), revisedSamplingInterval (float), revisedQueueSize (int) Module\Subscription\PublishResult: subscriptionId (int), sequenceNumber (int), moreNotifications (bool), notifications (array), availableSequenceNumbers (int[]) Module\Subscription\TransferResult: statusCode (int), availableSequenceNumbers (int[]) Types\ExtensionObject: typeId (NodeId), encoding (int), body (?string), value (mixed); methods: isDecoded(), isRaw(). DataValue::getValue() auto-extracts decoded value when codec is registered. Module\ServerInfo\BuildInfo: productName (?string), manufacturerName (?string), softwareVersion (?string), buildNumber (?string), buildDate (?DateTimeImmutable) Module\NodeManagement\AddNodesResult: statusCode (int), addedNodeId (NodeId) --- ## 9. Error Handling Exception hierarchy: ``` RuntimeException ??? OpcUaException ??? ConfigurationException ??? ConnectionException ??? EncodingException ??? InvalidNodeIdException ??? ProtocolException ? ??? HandshakeException ($errorCode) ? ??? MessageTypeException ($expected, $actual) ??? SecurityException ? ??? CertificateParseException ? ??? OpenSslException ? ??? SignatureVerificationException ? ??? UnsupportedCurveException ($curveName) ??? ServiceException (carries getStatusCode()) ??? UntrustedCertificateException ($fingerprint, $certDer) ??? WriteTypeDetectionException ??? WriteTypeMismatchException ($nodeId, $expectedType, $givenType) ``` Status codes vs exceptions: - Exceptions: connection failures, protocol errors, security failures - Status codes in results: per-item results from read/write/call (check $dv->statusCode) --- ## 10. Security RSA Policies: None, Basic128Rsa15, Basic256, Basic256Sha256, Aes128Sha256RsaOaep, Aes256Sha256RsaPss ECC Policies: EccNistP256 (P-256, AES-128, HMAC-SHA256), EccNistP384 (P-384, AES-256, HMAC-SHA384), EccBrainpoolP256r1 (BP-256, AES-128, HMAC-SHA256), EccBrainpoolP384r1 (BP-384, AES-256, HMAC-SHA384) Connection flow with RSA security: 1. Discovery ? connect without security, get server certificate 2. Asymmetric Phase ? OPN request encrypted with server's RSA public key, nonce exchange 3. Symmetric Phase ? all messages use derived keys (AES-CBC + HMAC), keys from P_SHA Connection flow with ECC security: 1. Discovery ? connect without security, get server ECC certificate 2. Asymmetric Phase ? OPN request signed only (ECDSA, no encryption), nonce = ephemeral ECDH pubkey (64/96 bytes X+Y) 3. Symmetric Phase ? keys derived via HKDF from ECDH shared secret, messages use AES-CBC + HMAC 4. Password auth uses EccEncryptedSecret: client requests ECDHKey via AdditionalHeader, encrypts password with ECDH+AES --- ## 11. Architecture ``` src/ ??? ClientBuilder.php # Builder / entry point (config traits, addModule, replaceModule) ??? ClientBuilderInterface.php # Builder interface ??? Client.php # Connected client (proxy to modules, hasMethod, hasModule, getRegisteredMethods, getLoadedModules, __call) ??? OpcUaClientInterface.php # Public API contract (all built-in methods + hasMethod + hasModule + getRegisteredMethods + getLoadedModules) ??? ClientBuilder/*.php # Builder traits (configuration: cache, events, timeout, trust, modules, etc.) ??? Kernel/ClientKernel.php # Shared infrastructure for modules ??? Kernel/ClientKernelInterface.php # Kernel contract for modules ??? Kernel/ModuleRegistry.php # Module lifecycle, dependency sort, method registry ??? Module/ServiceModule.php # Abstract base class (register, boot, reset, requires) ??? Module/ReadWrite/ # ReadWriteModule + ReadService, WriteService, CallService, CallResult ??? Module/Browse/ # BrowseModule + BrowseService, GetEndpointsService, BrowseResultSet ??? Module/Subscription/ # SubscriptionModule + SubscriptionService, MonitoredItemService, PublishService, SubscriptionResult, MonitoredItemResult, PublishResult, TransferResult ??? Module/History/ # HistoryModule + HistoryReadService ??? Module/NodeManagement/ # NodeManagementModule + NodeManagementService, AddNodesResult ??? Module/TranslateBrowsePath/ # TranslateBrowsePathModule + TranslateBrowsePathService, BrowsePathResult ??? Module/ServerInfo/ # ServerInfoModule + BuildInfo ??? Module/TypeDiscovery/ # TypeDiscoveryModule ??? Transport/TcpTransport.php # TCP socket I/O ??? Encoding/BinaryEncoder.php # Serialization ??? Encoding/BinaryDecoder.php # Deserialization (accepts optional ExtensionObjectRepository) ??? Encoding/ExtensionObjectCodec.php # Codec interface ??? Protocol/AbstractProtocolService.php # Shared encode/decode base class ??? Protocol/ServiceTypeId.php # Named constants for all OPC UA service NodeIds ??? Protocol/SessionService.php # Session management (kernel-level) ??? Security/*.php # SecureChannel, MessageSecurity, CertificateManager, enums ??? Types/*.php # Shared types (NodeId, DataValue, Variant, etc. ? module-specific DTOs live in Module/*) ??? Builder/*.php # Fluent builders for multi-operations ??? Event/NullEventDispatcher.php # No-op PSR-14 dispatcher ??? Event/*.php # 38 event classes ??? Cache/InMemoryCache.php # PSR-16 in-memory cache ??? Cache/FileCache.php # PSR-16 file-based cache ??? Repository/ExtensionObjectRepository.php # Per-client codec registry ??? Wire/WireSerializable.php # Interface for JSON-IPC-safe value-objects (jsonSerialize + fromWireArray + wireTypeId) ??? Wire/WireTypeRegistry.php # Registry / encoder / decoder, security gate for __t discriminators (rejects unregistered ids at decode) ??? Wire/CoreWireTypes.php # Registers the cross-cutting core types on a WireTypeRegistry ??? Testing/MockClient.php # In-memory test double (no TCP, hasMethod, hasModule) ??? Exception/*.php # Exception classes (includes ModuleConflictException, MissingModuleDependencyException) ``` Binary encoding: little-endian, length-prefixed strings, NodeId compact encoding, DateTime as 100ns intervals since 1601-01-01 UTC. ### Wire serialization for cross-process IPC (v4.2.0) `PhpOpcua\Client\Wire\WireTypeRegistry` + `WireSerializable` interface implement a JSON-based, gadget-chain-free serialization layer used by `opcua-session-manager` and future transports. - Every core / module DTO implements `WireSerializable` with `jsonSerialize()` (pure associative array, no `__t`), `fromWireArray(array)` (inverse), and `wireTypeId()` (stable short id). The registry wraps each emitted value with `{"__t": "<id>", ...}` at encode time and rejects unknown `__t` ids at decode time. - `CoreWireTypes::register()` installs NodeId, QualifiedName, LocalizedText, DataValue, Variant (with base64 ByteString), ExtensionObject (with base64 body), BrowseNode, ReferenceDescription, EndpointDescription, UserTokenPolicy plus enums BuiltinType, NodeClass, BrowseDirection, ConnectionState. - Each `ServiceModule::registerWireTypes(WireTypeRegistry)` override contributes its module-specific DTOs (SubscriptionResult, TransferResult, MonitoredItemResult, MonitoredItemModifyResult, PublishResult, SetTriggeringResult, CallResult, BrowsePathResult, BrowsePathTarget, BrowseResultSet, AddNodesResult, BuildInfo). - `ModuleRegistry::buildWireTypeRegistry()` composes core + module contributions ? the single method a remote peer uses to mirror the allowlist. - Both backed enums (`::from($scalar)`) and pure unit enums (case-name scan) are supported. - `DateTimeImmutable` is a built-in special case (`{"__t": "DateTime", "v": "<ISO 8601 with microseconds>"}`). - Security: no `unserialize()` anywhere on the wire path ? only explicitly registered classes can be instantiated, gadget-chain surface is zero by construction. --- ## 12. ExtensionObject Codecs ```php class MyPointCodec implements ExtensionObjectCodec { public function decode(BinaryDecoder $decoder): array { return ['x' => $decoder->readDouble(), 'y' => $decoder->readDouble(), 'z' => $decoder->readDouble()]; } public function encode(BinaryEncoder $encoder, mixed $value): void { $encoder->writeDouble($value['x']); $encoder->writeDouble($value['y']); $encoder->writeDouble($value['z']); } } ``` Register per-client (not static): ```php $repo = new ExtensionObjectRepository(); $repo->register(NodeId::numeric(2, 5001), MyPointCodec::class); $client = ClientBuilder::create($repo) ->connect('opc.tcp://localhost:4840'); ``` Or on the builder before connecting: ```php $builder = ClientBuilder::create(); $builder->getExtensionObjectRepository()->register(NodeId::numeric(2, 5001), MyPointCodec::class); $client = $builder->connect('opc.tcp://localhost:4840'); ``` Repository API: register(), has(), get(), unregister(), clear() ### Automatic DataType Discovery Instead of writing codecs manually, call discoverDataTypes() after connecting: ```php $client = ClientBuilder::create() ->connect('opc.tcp://localhost:4840'); $client->discoverDataTypes(); $point = $client->read($pointNodeId)->getValue(); // ['x' => 1.5, 'y' => 2.5, 'z' => 3.5] ? decoded automatically ``` Filter by namespace: `$client->discoverDataTypes(namespaceIndex: 2)` Manually registered codecs are never overwritten by auto-discovery. Requires OPC UA 1.04+ servers that expose DataTypeDefinition attributes. Limitations: binary encoding only (not XML), no built-in codecs shipped. Each Client is isolated. --- ## 13. MockClient (Testing) `PhpOpcua\Client\Testing\MockClient` implements `OpcUaClientInterface` without any TCP connection. Use it to test code that depends on an OPC UA client without needing a real server. ### Creating a MockClient ```php use PhpOpcua\Client\Testing\MockClient; $client = MockClient::create(); ``` ### Registering Handlers ```php use PhpOpcua\Client\Types\DataValue; use PhpOpcua\Client\Types\NodeId; // Read handler ? receives the NodeId, returns a DataValue $client->onRead(function (NodeId $nodeId) { return DataValue::ofDouble(23.5); }); // Write handler ? receives NodeId, value, type; returns status code $client->onWrite(function (NodeId $nodeId, mixed $value, BuiltinType $type) { return StatusCode::Good; }); // Browse handler ? receives NodeId, returns ReferenceDescription[] $client->onBrowse(function (NodeId $nodeId) { return []; }); // Call handler ? receives objectId, methodId, arguments; returns CallResult $client->onCall(function (NodeId $objectId, NodeId $methodId, array $args) { return new CallResult(statusCode: 0, inputArgumentResults: [], outputArguments: []); }); // Path resolution handler $client->onResolveNodeId(function (string $path) { return NodeId::numeric(2, 1001); }); ``` ### Default Behaviors Unregistered operations return sensible defaults: empty arrays for browse, status Good for writes, empty DataValue for reads. ### Call Tracking ```php $client->read('ns=2;s=Temperature'); $client->read('ns=2;s=Pressure'); $client->write('ns=2;i=1001', 42, BuiltinType::Int32); $client->getCalls(); // all recorded calls $client->getCallsFor('read'); // only read calls $client->callCount('read'); // 2 $client->callCount('write'); // 1 $client->resetCalls(); // clear all recorded calls ``` ### Works with Fluent Builders ```php $client = MockClient::create(); $client->onRead(fn (NodeId $n) => DataValue::ofInt32(42)); $results = $client->readMulti() ->node('i=2259')->value() ->node('ns=2;i=1001')->value() ->execute(); ``` --- ## 14. Events (PSR-14) The client dispatches 47 granular PSR-14 events. Inject any EventDispatcherInterface via $builder->setEventDispatcher($dispatcher) on the ClientBuilder. NullEventDispatcher is used by default (zero overhead ? event objects lazily instantiated via closures). ### Configuration ```php $builder = ClientBuilder::create(); $builder->setEventDispatcher($yourDispatcher); $builder->getEventDispatcher(); // returns current dispatcher $client = $builder->connect('opc.tcp://localhost:4840'); ``` ### Event Categories Connection (6): ClientConnecting, ClientConnected, ConnectionFailed, ClientReconnecting, ClientDisconnecting, ClientDisconnected Session (3): SessionCreated, SessionActivated, SessionClosed Secure Channel (2): SecureChannelOpened, SecureChannelClosed Subscription (9): SubscriptionCreated, SubscriptionDeleted, SubscriptionTransferred, MonitoredItemCreated, MonitoredItemDeleted, DataChangeReceived, EventNotificationReceived, PublishResponseReceived, SubscriptionKeepAlive Alarms ? generic (1): AlarmEventReceived (subscriptionId, clientHandle, eventFields, severity, sourceName, message, eventType, time) Alarms ? specific (8): AlarmActivated, AlarmDeactivated, AlarmAcknowledged, AlarmConfirmed, AlarmShelved, AlarmSeverityChanged, LimitAlarmExceeded, OffNormalAlarmTriggered Read/Write/Browse (4): NodeValueRead, NodeValueWritten, NodeValueWriteFailed, NodeBrowsed Type Discovery (1): DataTypesDiscovered Cache (2): CacheHit, CacheMiss Retry (2): RetryAttempt, RetryExhausted All events are readonly classes in PhpOpcua\Client\Event\. All carry a $client property (OpcUaClientInterface). ### Alarm Deduction Alarm-specific events are deduced from event notification fields: - Default 6 fields (EventId, EventType, SourceName, Time, Message, Severity) trigger AlarmEventReceived and AlarmSeverityChanged - ActiveState field ? AlarmActivated / AlarmDeactivated - AckedState field ? AlarmAcknowledged - ConfirmedState field ? AlarmConfirmed - ShelvingState field ? AlarmShelved - EventType matching known LimitAlarm NodeIds ? LimitAlarmExceeded - EventType matching known OffNormalAlarm NodeIds ? OffNormalAlarmTriggered ### Key Classes - PhpOpcua\Client\Event\NullEventDispatcher ? no-op PSR-14 dispatcher (default) - PhpOpcua\Client\ClientBuilder\ManagesEventDispatcherTrait ? builder trait with setEventDispatcher(), getEventDispatcher() - PhpOpcua\Client\Client\ManagesEventDispatchTrait ? client trait with runtime dispatch() helper --- ## Main Classes - PhpOpcua\Client\ClientBuilder ? builder / entry point, implements ClientBuilderInterface, static create() factory, addModule(), replaceModule() - PhpOpcua\Client\ClientBuilderInterface ? builder interface (configuration methods + connect + addModule + replaceModule) - PhpOpcua\Client\Client ? connected client (proxy to modules), implements OpcUaClientInterface, hasMethod(), hasModule(), getRegisteredMethods(), getLoadedModules(), __call() for custom module methods - PhpOpcua\Client\OpcUaClientInterface ? public API contract (all built-in service methods + hasMethod(string): bool + hasModule(string): bool + getRegisteredMethods(): string[] + getLoadedModules(): class-string[]) - PhpOpcua\Client\Kernel\ClientKernel ? shared infrastructure for modules (executeWithRetry, ensureConnected, send, receive, createDecoder, dispatch, logContext) - PhpOpcua\Client\Kernel\ClientKernelInterface ? kernel contract that modules depend on - PhpOpcua\Client\Kernel\ModuleRegistry ? module lifecycle, topological dependency sort, method conflict detection - PhpOpcua\Client\Module\ServiceModule ? abstract base class for modules (register, boot, reset, requires) - PhpOpcua\Client\Types\NodeId ? public readonly: namespaceIndex, identifier, type - PhpOpcua\Client\Types\Variant ? public readonly: type, value, dimensions - PhpOpcua\Client\Types\DataValue ? public readonly: statusCode, sourceTimestamp, serverTimestamp; method: getValue() - PhpOpcua\Client\Types\ReferenceDescription ? public readonly: referenceTypeId, isForward, nodeId, browseName, displayName, nodeClass, typeDefinition - PhpOpcua\Client\Module\Subscription\SubscriptionResult ? public readonly: subscriptionId, revisedPublishingInterval, revisedLifetimeCount, revisedMaxKeepAliveCount - PhpOpcua\Client\Module\Subscription\MonitoredItemResult ? public readonly: statusCode, monitoredItemId, revisedSamplingInterval, revisedQueueSize - PhpOpcua\Client\Module\ReadWrite\CallResult ? public readonly: statusCode, inputArgumentResults, outputArguments - PhpOpcua\Client\Module\Subscription\PublishResult ? public readonly: subscriptionId, sequenceNumber, moreNotifications, notifications - PhpOpcua\Client\Module\Subscription\TransferResult ? public readonly: statusCode, availableSequenceNumbers - PhpOpcua\Client\Module\Browse\BrowseResultSet ? public readonly: references, continuationPoint - PhpOpcua\Client\Module\TranslateBrowsePath\BrowsePathResult ? public readonly: statusCode, targets - PhpOpcua\Client\Types\BrowsePathTarget ? public readonly: targetId, remainingPathIndex - PhpOpcua\Client\Module\NodeManagement\AddNodesResult ? public readonly: statusCode, addedNodeId (NodeId) - PhpOpcua\Client\Module\ServerInfo\BuildInfo ? public readonly: productName, manufacturerName, softwareVersion, buildNumber, buildDate - PhpOpcua\Client\Event\NullEventDispatcher ? no-op PSR-14 dispatcher (default, zero overhead) - PhpOpcua\Client\Exception\ModuleConflictException ? thrown when two modules register the same method name - PhpOpcua\Client\Exception\MissingModuleDependencyException ? thrown when a module's required dependency is not registered - PhpOpcua\Client\Exception\WriteTypeDetectionException ? thrown when write type cannot be auto-detected - PhpOpcua\Client\Exception\WriteTypeMismatchException ? thrown when explicit write type mismatches detected type (public readonly: nodeId, expectedType, givenType) - PhpOpcua\Client\Event\WriteTypeDetecting ? dispatched before write type detection starts (public readonly: client, nodeId) - PhpOpcua\Client\Event\WriteTypeDetected ? dispatched after write type detection (public readonly: client, nodeId, detectedType, fromCache) - PhpOpcua\Client\Event\* ? 42 readonly event classes with $client property (ClientConnecting, ClientConnected, ConnectionFailed, SessionCreated, SessionActivated, SubscriptionCreated, DataChangeReceived, EventNotificationReceived, AlarmEventReceived, AlarmActivated, AlarmDeactivated, NodeValueRead, NodeValueWritten, NodeBrowsed, WriteTypeDetecting, WriteTypeDetected, CacheHit, CacheMiss, RetryAttempt, etc.) - PhpOpcua\Client\Testing\MockClient ? in-memory test double (static create(), onRead/onWrite/onBrowse/onCall/onResolveNodeId, getCalls/getCallsFor/callCount/resetCalls, hasMethod, hasModule) - PhpOpcua\Client\Repository\ExtensionObjectRepository ? per-client codec registry (instance-level) - PhpOpcua\Client\Wire\WireSerializable ? interface (jsonSerialize, fromWireArray, wireTypeId) implemented by every core / module DTO - PhpOpcua\Client\Wire\WireTypeRegistry ? security gate + encoder/decoder; rejects unregistered __t discriminators at decode time, uniform for BackedEnum + pure UnitEnum + DateTimeImmutable - PhpOpcua\Client\Wire\CoreWireTypes ? installs cross-cutting core types (NodeId, QualifiedName, LocalizedText, DataValue, Variant, ExtensionObject, BrowseNode, ReferenceDescription, EndpointDescription, UserTokenPolicy + 4 enums) on a WireTypeRegistry - PhpOpcua\Client\Encoding\DynamicCodec ? auto-generated codec from StructureDefinition - PhpOpcua\Client\Encoding\DataTypeMapping ? maps DataType NodeIds to BuiltinTypes - PhpOpcua\Client\Encoding\StructureDefinitionParser ? parses DataTypeDefinition attributes - PhpOpcua\Client\Types\StructureField ? public readonly: name, dataType, valueRank, builtinType, isOptional - PhpOpcua\Client\Types\StructureDefinition ? public readonly: defaultEncodingId, baseDataType, structureType, fields - PhpOpcua\Client\Types\ExtensionObject ? typed DTO for OPC UA ExtensionObject (public readonly: typeId, encoding, body, value; methods: isDecoded(), isRaw()). DataValue::getValue() auto-extracts decoded value. - PhpOpcua\Client\Protocol\AbstractProtocolService ? shared base class for protocol services (encodeRequestAuto, writeRequestHeader, readResponseMetadata, wrapInMessage) - PhpOpcua\Client\Protocol\ServiceTypeId ? final class with named constants for all OPC UA service NodeIds (35 constants) ## Related Packages - php-opcua/opcua-client-nodeset: pre-generated PHP types from 51 OPC Foundation companion specifications ? 807 files, 338 enums, 191 DTOs, 191 codecs, registrars with automatic dependency resolution - php-opcua/opcua-session-manager: session persistence across PHP requests - php-opcua/laravel-opcua: Laravel integration (service provider, facade, config) - php-opcua/uanetstandard-test-suite: Docker-based OPC UA test servers (UA-.NETStandard) ## Alternatives PHP: techdock/opcua (PHP 8.4+, heavier deps, v0.2), techdock/opcua-webapi-client (HTTP gateway), QuickOPC (commercial, Windows COM) Cross-language: node-opcua (TS), opcua-asyncio (Python), UA-.NETStandard (C#), gopcua (Go), open62541 (C) ## Links - Repository: https://github.com/php-opcua/opcua-client - Documentation: https://github.com/php-opcua/opcua-client/tree/master/doc - Issues: https://github.com/php-opcua/opcua-client/issues - Packagist: https://packagist.org/packages/php-opcua/opcua-client - Changelog: https://github.com/php-opcua/opcua-client/blob/master/CHANGELOG.md