Skip to content

v3.0.0

Choose a tag to compare

@GianfriAur GianfriAur released this 22 Mar 16:18
· 96 commits to master since this release

Changed

  • ExtensionObjectRepository is now instance-level instead of static. Each Client has its own isolated codec registry. Pass it via the constructor (new Client(extensionObjectRepository: $repo)) or access it with $client->getExtensionObjectRepository(). Codecs registered on one client no longer affect other clients in the same process.

  • Strict return types for all service responses. The following methods now return typed DTOs with public readonly properties instead of associative arrays:

    • createSubscription()SubscriptionResult (->subscriptionId, ->revisedPublishingInterval, ->revisedLifetimeCount, ->revisedMaxKeepAliveCount)
    • createMonitoredItems()MonitoredItemResult[] (->statusCode, ->monitoredItemId, ->revisedSamplingInterval, ->revisedQueueSize)
    • createEventMonitoredItem()MonitoredItemResult
    • call()CallResult (->statusCode, ->inputArgumentResults, ->outputArguments)
    • browseWithContinuation() / browseNext()BrowseResultSet (->references, ->continuationPoint)
    • publish()PublishResult (->subscriptionId, ->sequenceNumber, ->moreNotifications, ->notifications)
    • translateBrowsePaths()BrowsePathResult[] (->statusCode, ->targets) with BrowsePathTarget (->targetId, ->remainingPathIndex)
  • All existing Type classes now expose public readonly properties. You can access $ref->nodeId, $ref->displayName, $variant->type, $dv->statusCode, etc. directly instead of calling getter methods. Affected classes: NodeId, Variant, DataValue, QualifiedName, LocalizedText, ReferenceDescription, EndpointDescription, UserTokenPolicy, BrowseNode.

  • nodeClassMask parameter replaced with nodeClasses array. Browse methods (browse(), browseWithContinuation(), browseAll(), browseRecursive()) now accept NodeClass[] $nodeClasses = [] instead of int $nodeClassMask = 0. Pass an array of NodeClass enum values (e.g., [NodeClass::Object, NodeClass::Variable]) instead of a raw bitmask integer. Empty array means all classes (same as the old 0).

  • Ambiguous $items parameters renamed for named parameter clarity: readMulti($readItems), writeMulti($writeItems), createMonitoredItems($subscriptionId, $monitoredItems).

  • PHP 8.5 added to the CI test matrix.

Added

  • SubscriptionResult, MonitoredItemResult, CallResult, BrowseResultSet, PublishResult, BrowsePathResult, BrowsePathTarget DTO classes in Types/.
  • Client::getExtensionObjectRepository() method on Client and OpcUaClientInterface.
  • Client constructor now accepts an optional ?ExtensionObjectRepository $extensionObjectRepository parameter.
  • BinaryDecoder constructor now accepts an optional ?ExtensionObjectRepository parameter for codec resolution.
  • 800+ unit and integration tests with 99.5%+ code coverage.
  • PSR-3 Logging. Inject any PSR-3 compatible logger (Monolog, Laravel, etc.) via $client->setLogger($logger) or the constructor. Logs connection events (INFO), retry attempts (WARNING), batch splits (INFO), failures (ERROR), and protocol details (DEBUG). Uses NullLogger by default.
  • psr/log ^3.0 added as dependency (interface-only package, zero runtime code).
  • PSR-16 Cache for browse results. Browse, browseAll, and resolveNodeId results are cached by default using an in-memory PSR-16 cache (300s TTL). Pass useCache: false to bypass the cache on any call, or plug in any PSR-16 driver (Laravel Cache, Redis, etc.) via $client->setCache($driver). Ships with InMemoryCache and FileCache. Use invalidateCache($nodeId) or flushCache() to manage entries.
  • psr/simple-cache ^3.0 added as dependency (interface-only package, zero runtime code).
  • InMemoryCache — PSR-16 in-memory cache implementation with configurable TTL.
  • FileCache — PSR-16 file-based cache implementation that survives process restarts.
  • ManagesCacheTrait — trait providing setCache(), getCache(), invalidateCache(), flushCache() and internal cache key generation.
  • getEndpoints() results are now cached. Pass useCache: false to bypass.
  • discoverDataTypes() results are now cached. On cache hit, discovered type definitions are replayed from cache (registers codecs without server round-trips). Especially useful with FileCache to persist discovered types across PHP process restarts. Pass useCache: false to bypass.
  • MockClient for testing. A drop-in OpcUaClientInterface implementation with no TCP connection. Register response handlers with onRead(), onWrite(), onBrowse(), onCall(), onResolveNodeId(). Track calls with getCalls(), callCount(), getCallsFor().
  • DataValue factory methods. DataValue::ofInt32(42), ofDouble(3.14), ofString('hello'), ofBoolean(true), of($value, BuiltinType), bad(StatusCode).
  • Automatic DataType discovery. $client->discoverDataTypes() browses the server's DataType hierarchy, reads DataTypeDefinition attributes (OPC UA 1.04+), and automatically creates DynamicCodec instances for all server-defined structured types. Eliminates the need to manually implement codecs for custom types. Supports Structure, StructureWithOptionalFields, and Union types.
  • StructureField, StructureDefinition DTOs in Types/ for representing discovered type definitions.
  • DynamicCodec — a generic ExtensionObjectCodec that decodes/encodes based on a StructureDefinition.
  • DataTypeMapping — maps OPC UA DataType NodeIds to BuiltinType enum values.
  • transferSubscriptions() — transfer existing subscriptions to a new session after reconnection without data loss. Returns TransferResult[] with status codes and available sequence numbers.
  • republish() — re-request notifications that were sent but not yet acknowledged. Essential for the session manager to recover from session loss.
  • TransferResult DTO in Types/.
  • StructureDefinitionParser — parses the binary body of StructureDefinition ExtensionObjects.
  • BinaryDecoder::readVariantValue() is now public (was private).
  • Fluent/Builder API for multi-node operations. readMulti(), writeMulti(), createMonitoredItems(), and translateBrowsePaths() now return a fluent builder when called without arguments: $client->readMulti()->node('i=2259')->value()->node('i=1001')->displayName()->execute(). The array-based API still works when passing arguments directly.
  • All methods accepting NodeId now also accept string. Pass OPC UA string format directly (e.g., 'i=2259', 'ns=2;i=1001', 'ns=2;s=MyNode'). Applies to: read, write, browse, browseAll, browseWithContinuation, browseRecursive, call (both params), historyReadRaw, historyReadProcessed, historyReadAtTime, createEventMonitoredItem, resolveNodeId ($startingNodeId). Also works inside arrays for readMulti, writeMulti, createMonitoredItems, translateBrowsePaths.

Deprecated

  • Getter methods on Type classes that are now redundant with public readonly properties. All existing getters still work but are marked @deprecated. Affected methods include getNodeId(), getDisplayName(), getBrowseName(), getNodeClass(), getStatusCode() (on DataValue), getSourceTimestamp(), getServerTimestamp(), getVariant(), getType() (on Variant), getValue() (on Variant), getDimensions(), getNamespaceIndex(), getIdentifier(), getLocale(), getText(), and all getters on EndpointDescription, UserTokenPolicy, ReferenceDescription, BrowseNode. Use direct property access (->property) instead.

Breaking Changes

  • All service response methods listed above now return typed objects instead of arrays. Code using $result['key'] must change to $result->key.
  • ExtensionObjectRepository methods (register, get, has, unregister, clear) are no longer static. Replace ExtensionObjectRepository::register(...) with $repo->register(...).
  • Browse methods no longer accept int $nodeClassMask. Use array $nodeClasses with NodeClass enum values instead. Replace nodeClassMask: 3 with nodeClasses: [NodeClass::Object, NodeClass::Variable].
  • readMulti($items) renamed to readMulti($readItems), writeMulti($items) to writeMulti($writeItems), createMonitoredItems(..., $items) to createMonitoredItems(..., $monitoredItems). Only affects code using named parameters.