v3.0.0
Changed
-
ExtensionObjectRepositoryis now instance-level instead of static. EachClienthas 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 readonlyproperties instead of associative arrays:createSubscription()→SubscriptionResult(->subscriptionId,->revisedPublishingInterval,->revisedLifetimeCount,->revisedMaxKeepAliveCount)createMonitoredItems()→MonitoredItemResult[](->statusCode,->monitoredItemId,->revisedSamplingInterval,->revisedQueueSize)createEventMonitoredItem()→MonitoredItemResultcall()→CallResult(->statusCode,->inputArgumentResults,->outputArguments)browseWithContinuation()/browseNext()→BrowseResultSet(->references,->continuationPoint)publish()→PublishResult(->subscriptionId,->sequenceNumber,->moreNotifications,->notifications)translateBrowsePaths()→BrowsePathResult[](->statusCode,->targets) withBrowsePathTarget(->targetId,->remainingPathIndex)
-
All existing Type classes now expose
public readonlyproperties. 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. -
nodeClassMaskparameter replaced withnodeClassesarray. Browse methods (browse(),browseWithContinuation(),browseAll(),browseRecursive()) now acceptNodeClass[] $nodeClasses = []instead ofint $nodeClassMask = 0. Pass an array ofNodeClassenum values (e.g.,[NodeClass::Object, NodeClass::Variable]) instead of a raw bitmask integer. Empty array means all classes (same as the old0). -
Ambiguous
$itemsparameters 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,BrowsePathTargetDTO classes inTypes/.Client::getExtensionObjectRepository()method onClientandOpcUaClientInterface.Clientconstructor now accepts an optional?ExtensionObjectRepository $extensionObjectRepositoryparameter.BinaryDecoderconstructor now accepts an optional?ExtensionObjectRepositoryparameter 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). UsesNullLoggerby 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: falseto bypass the cache on any call, or plug in any PSR-16 driver (Laravel Cache, Redis, etc.) via$client->setCache($driver). Ships withInMemoryCacheandFileCache. UseinvalidateCache($nodeId)orflushCache()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 providingsetCache(),getCache(),invalidateCache(),flushCache()and internal cache key generation.getEndpoints()results are now cached. PassuseCache: falseto bypass.discoverDataTypes()results are now cached. On cache hit, discovered type definitions are replayed from cache (registers codecs without server round-trips). Especially useful withFileCacheto persist discovered types across PHP process restarts. PassuseCache: falseto bypass.MockClientfor testing. A drop-inOpcUaClientInterfaceimplementation with no TCP connection. Register response handlers withonRead(),onWrite(),onBrowse(),onCall(),onResolveNodeId(). Track calls withgetCalls(),callCount(),getCallsFor().DataValuefactory 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, readsDataTypeDefinitionattributes (OPC UA 1.04+), and automatically createsDynamicCodecinstances for all server-defined structured types. Eliminates the need to manually implement codecs for custom types. Supports Structure, StructureWithOptionalFields, and Union types. StructureField,StructureDefinitionDTOs inTypes/for representing discovered type definitions.DynamicCodec— a genericExtensionObjectCodecthat decodes/encodes based on aStructureDefinition.DataTypeMapping— maps OPC UA DataType NodeIds toBuiltinTypeenum values.transferSubscriptions()— transfer existing subscriptions to a new session after reconnection without data loss. ReturnsTransferResult[]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.TransferResultDTO inTypes/.StructureDefinitionParser— parses the binary body ofStructureDefinitionExtensionObjects.BinaryDecoder::readVariantValue()is now public (was private).- Fluent/Builder API for multi-node operations.
readMulti(),writeMulti(),createMonitoredItems(), andtranslateBrowsePaths()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
NodeIdnow also acceptstring. 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 forreadMulti,writeMulti,createMonitoredItems,translateBrowsePaths.
Deprecated
- Getter methods on Type classes that are now redundant with
public readonlyproperties. All existing getters still work but are marked@deprecated. Affected methods includegetNodeId(),getDisplayName(),getBrowseName(),getNodeClass(),getStatusCode()(on DataValue),getSourceTimestamp(),getServerTimestamp(),getVariant(),getType()(on Variant),getValue()(on Variant),getDimensions(),getNamespaceIndex(),getIdentifier(),getLocale(),getText(), and all getters onEndpointDescription,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. ExtensionObjectRepositorymethods (register,get,has,unregister,clear) are no longer static. ReplaceExtensionObjectRepository::register(...)with$repo->register(...).- Browse methods no longer accept
int $nodeClassMask. Usearray $nodeClasseswithNodeClassenum values instead. ReplacenodeClassMask: 3withnodeClasses: [NodeClass::Object, NodeClass::Variable]. readMulti($items)renamed toreadMulti($readItems),writeMulti($items)towriteMulti($writeItems),createMonitoredItems(..., $items)tocreateMonitoredItems(..., $monitoredItems). Only affects code using named parameters.