Changelog
[4.3.1] - 2026-05-05
Added
TransportFactory::assertUnixPathFits(string $path)— throwsDaemonExceptionwhen a Unix-socket path exceedssun_pathcapacity (108 on Linux, 104 on Darwin). Replaces the silent kernel truncation that previously surfaced as a confusingchmod(): No such file or directory.TransportFactory::MAX_UNIX_PATH_LINUX/MAX_UNIX_PATH_DARWINconstants.
Changed
SessionManagerDaemon::run()validates the socket path length before binding; the error message reports the offending length and points atOPCUA_SOCKET_PATH.
Tests
- +4 unit tests in
TransportFactoryTest.php;
[4.3.0] - 2026-04-24
Changed
- Bumped
php-opcua/opcua-clientfrom^4.2.0to^4.3.0. - Bumped CI test-server suite
[email protected]→@v1.2.0. - Documentation — realigned doc/ and README version mentions to
^4.3.0and renamed stale "v4.0.0 DTOs" references to "module DTOs" (DTOs were relocated to their module namespaces in v4.2.0).
Added
--version/-vflag onbin/opcua-session-managerprints the daemon version. Version exposed asSessionManagerDaemon::VERSION.ServiceUnsupportedExceptionis now properly propagated through the IPC boundary. The daemon's error serializer already emits the short class name; theManagedClientIPC decoder now recognisesServiceUnsupportedExceptionand re-throws it as the correct subclass instead of degrading it to a genericDaemonException. Relevant whenever a server does not implement a requested service set (typical case:NodeManagementModuleoperations against UA-.NETStandard, which returnsBadServiceUnsupported).catch (ServiceUnsupportedException $e)in user code now works as expected without string-matching on the message.- New
src/Cli/ArgvParserclass andtests/Unit/Cli/ArgvParserTest.phpcovering the daemon's CLI argument parsing. The previous inline parser inbin/opcua-session-managersilently dropped flags whose value was missing (e.g.--socketas last argument); the extracted parser now emitsMissing value for option <flag>and the bin exits with code 1 instead of starting with a half-applied config. Backwards-compat preserved for unknown flags (still ignored). - New
tests/Unit/ManagedClientTcpTest.php— cross-OS coverage of the IPC error-mapping path via the loopback TCP transport. Usesproc_open()instead ofpcntl_fork()and binds ontcp://127.0.0.1:0, so it runs on Linux, macOS, and Windows. The existingManagedClientIpcTest.phpstays Unix-only (->skipOnWindows()) as before; together the two files provide error-mapping coverage on every matrix leg.
Changed
CommandHandler::handleCommand()— the error-class short-name computation now uses(new ReflectionClass($e))->getShortName()instead of the previousbasename(str_replace('\\', '/', get_class($e)))hack. Equivalent output, idiomatic and resilient to futureget_class()edge cases.bin/opcua-session-manager— the 60-line inline argv loop has been extracted intosrc/Cli/ArgvParser::parse(), leaving the bin as a short glue script that handles action dispatch, env-var overrides, interdependencies (e.g.--cache-pathrequired when--cache-driver=file), and daemon bootstrap.
Security
- Socket file permission race closed.
SessionManagerDaemon::run()now callsumask(0077)around theSocketServerbind so the Unix socket is created0600atomically. Previously a permissive process umask could leave the socket world-readable/writable in the window betweenbind()and the follow-upchmod(), and a daemon crash in that window could leave a permissive leftover on disk. usernameno longer leaked via thelistIPC command. Added'username'toCommandHandler::SENSITIVE_CONFIG_KEYS; the session-lookup cache key (SessionConfig::sanitized()) still preserves username to keep sessions properly scoped per user. A local peer callinglistcan no longer enumerate(endpoint → username)tuples of other sessions, which previously enabled targeted credential-stuffing against those endpoints.- Per-frame NDJSON cap on inbound IPC. Added
SessionManagerDaemon::MAX_FRAME_BYTES = 65_536and a length check beforejson_decode(). Closes a DoS partiale where a single client could force repeated parsing of ~1 MiB of JSON per connection (MAX_BUFFER_SIZE) × 50 concurrent connections. Legitimate requests are under 2 KiB, 64 KiB is comfortable headroom. - IPv4-mapped IPv6 loopback now accepted/rejected consistently.
TcpLoopbackTransport::isLoopbackAddress()previously rejected::ffff:127.0.0.1(false negative) and would have misclassified::ffff:192.168.1.10as non-loopback only by coincidence. Explicit handling added:::ffff:127.*accepted, everything else under::ffff:rejected at construction. sanitizeErrorMessageredacts Windows paths and URLs. The previous Unix-only regex letC:\Users\...\secret.pemand URLs with embedded credentials (opc.tcp://user:pwd@host) leak unchanged. Three regexes now run: URL (any scheme), Windows path, Unix path; each emits[url]/[path]. Regression tests added inCommandHandlerSecurityTest.- PID check conservative fallback.
SessionManagerDaemon::isProcessRunning()now treats "can neither callposix_killnor read/proc" as "process alive" instead of "dead". Prevents a new daemon from stealing the PID file from a live instance on sandboxed environments where both introspection paths are denied. - Persistent cache hardening.
opcua-clientv4.3.0 removedunserialize()from every cache code path in favour of JSON gated by an allowlist (Cache\WireCacheCodec). The daemon is long-running and its per-session caches persist across requests, so upgrade paths that share a cache backend across processes should flush it once on upgrade. No API change for the daemon itself — the newCacheCodecInterfaceis picked up automatically via the defaultClientBuilder.
[4.2.0] - 2026-04-17
Changed
- Bumped minimum
php-opcua/opcua-clientfrom^4.1to^4.2.0. AlignsManagedClientwith the new Kernel + ServiceModule architecture and unlocks the Wire-serialization pipeline below. - Module-specific DTO imports updated:
SubscriptionResult,TransferResult,MonitoredItemResult,MonitoredItemModifyResult,PublishResult,SetTriggeringResult,CallResult,BrowsePathResult,BrowsePathTarget,BrowseResultSetnow live in their module namespaces (PhpOpcua\Client\Module\*) rather thanPhpOpcua\Client\Types\*. Affected files:Serialization/TypeSerializer.php,Client/ManagedClient.php, and the corresponding unit tests.
Added
- Transport-layer abstraction in
src/Ipc/to unblock non-Unix-socket IPC (Windows in particular) without touching the wire format:TransportInterface— narrow send/receive-line API suitable for NDJSON framing. Opens streams in binary mode so that Windows text-mode\n↔\r\ntranslation never silently mangles frame boundaries.AbstractStreamTransport— shared NDJSON loop on top of a PHP stream resource. Concrete transports only implementopenStream().UnixSocketTransport— the established default on Linux / macOS. Connects tounix://<path>.TcpLoopbackTransport— portable alternative. Refuses to bind to anything outside127.0.0.0/8or::1/localhostat construction time, preserving the "trusted local origin" posture that the Unix socket file permissions grant today. Primarily aimed at Windows, where ReactPHP's Unix-socket support is still partial.WireMessageCodec— NDJSON-framed typed-envelope codec. Requests:{id, t: "req", method, args}, responses:{id, t: "res", ok: true/false, data | error}. Rejects frames larger than 16 MiB or nested deeper than 32 levels (DoS gate). Strict on envelope shape so that wire drift is loud instead of silently masked.
- Generic method dispatch over IPC — third-party modules work out of the box. Two new
CommandHandlercommands plus a correspondingManagedClientpath turn the daemon into a fully introspectable RPC surface:describe— returns{methods, modules, wireClasses, enumClasses, wireTypeIds}for the session's underlying client. The ManagedClient caches the response for the lifetime of the session and uses it to populatehasMethod()/hasModule()/getRegisteredMethods()/getLoadedModules()without further round-trips.invoke— generic dispatch:{method, args: [<wire-encoded>…]}. Args are decoded with aWireTypeRegistrybuilt from the daemon'smoduleRegistry->buildWireTypeRegistry(); the result is encoded the same way. Unlikequery,invokeis not gated by a static method whitelist — instead,$client->hasMethod($method)is the authoritative check, and the wire registry is the authoritative allowlist for typed payload classes. Third-party modules registered on the daemon viaClientBuilder::addModule()become callable fromManagedClient::$name(...)with no further plumbing.ManagedClient::__call()— proxies any method that is not concretely declared (e.g. a customacme:queryFirstprovided by a third-party module) through theinvokepath. Declared methods continue to use the existing typed-command path (query+TypeSerializer) unchanged — this release is strictly additive.- NodeManagement methods (
addNodes,deleteNodes,addReferences,deleteReferences) — previously guarded byBadMethodCallException— now delegate throughinvoke, so they succeed whenever the daemon has opted intoNodeManagementModuleviaClientBuilder::addModule(new NodeManagementModule()).
Security
- Loopback-only TCP transport.
TcpLoopbackTransportrejects non-loopback hosts at construction time. Remote access — if ever wanted — must be layered explicitly (TLS, SSH tunnel). - Frame size and depth caps on every decode: 16 MiB per frame and 32-level JSON nesting. Bounded memory even against authenticated but hostile peers.
invokedispatch is gated byhasMethod: only methods the daemon's module set has actually registered are reachable; arbitrary method names are rejected withunknown_method.
Tests
- 37 new unit tests in
tests/Unit/Ipc/cover framing / codec / transport guard rails, 6 more intests/Unit/CommandHandlerDescribeInvokeTest.phpcover the describe / invoke dispatch. 10 additionalTransportFactoryTestcases + 7SessionManagerDaemonTransportTestcases cover endpoint URI parsing and the loopback-only guard on both sides. 9ParamDeserializerRegistryTestcases + 10SessionConfigTestcases cover the two v4.2.0 refactors below. Full suite: 539 passing, 0 failing (up from 456). tests/Unit/SocketConnectionTest.phpandtests/Unit/ManagedClientIpcTest.php— the test harness fork-based fake daemons usepcntl_fork, so the affected cases are marked->skipOnWindows(); the rest of the unit suite runs cross-OS.
Refactoring
SessionConfigDTO. Consumption of theconfigassociative array inCommandHandler::handleOpen()now goes through a typed readonlyPhpOpcua\SessionManager\Daemon\SessionConfigDTO (fromArray/toArray/sanitized()).handleOpen()no longer reads$config['opcuaTimeout'] ?? null/isset($config['...'])— every knob is a typed property on the DTO. A dedicatedbuildClientFromConfig()helper consumes the DTO and returns the connectedClient. Wire format is unchanged (still a plain JSON object for backwards compatibility); the conversion happens at the IPC boundary viaSessionConfig::fromArray().- Pluggable param deserializer. The 200-line
matchthat used to live insideCommandHandler::deserializeParams()is now aPhpOpcua\SessionManager\Serialization\ParamDeserializerRegistrythat delegates to one or moreParamDeserializerInterfaceimplementations consulted in registration order. The shipped behaviour lives inBuiltInParamDeserializer, covering every method in the default whitelist. Third-party modules that register custom service methods on the daemon's client can now ship a matchingParamDeserializerInterfaceand wire it up withCommandHandler::registerParamDeserializer()— no more patching the command handler.CommandHandlerdrops itsDateTimeImmutable,BrowseDirection,NodeClass,BuiltinType,ConnectionState,NodeIdimports as a side effect.
Windows support
TransportFactory(new,src/Ipc/TransportFactory.php) — central client-side factory that turns an endpoint string into the correctTransportInterface:unix:///absolute/path.sock→UnixSocketTransporttcp://127.0.0.1:<port>/tcp://[::1]:<port>→TcpLoopbackTransport(non-loopback hosts rejected at construction)- scheme-less path →
UnixSocketTransport(backwards-compatible with the pre-v4.2.0--socket /tmp/foo.sockconvention) TransportFactory::defaultEndpoint()picks per-OS:unix:///tmp/opcua-session-manager.sockon Linux/macOS,tcp://127.0.0.1:9990on Windows
SocketConnection::send()rewritten on top ofTransportInterface— one code path for every transport; drops ~80 lines of inline socket plumbing.SocketConnection::sendVia(TransportInterface, array)— new helper for callers that hold a long-lived transport (future pooled connections).SessionManagerDaemonlistener accepts bothunix://andtcp://URIs viaReact\Socket\SocketServer. PID file path is resolved per-transport (next to the socket file for Unix, insys_get_temp_dir()keyed by endpoint slug for TCP).chmod+ file cleanup run only for Unix endpoints. A construction-timeassertLoopbackIfTcp()guard refuses anytcp://0.0.0.0:…/ public-host binding, matching the TCP transport's client-side posture.config/defaults.phpdefaultsocket_pathnow usesTransportFactory::defaultEndpoint()so deploying on Windows picks up TCP loopback automatically without a config edit.bin/opcua-session-manager --helpupdated —--socket <uri>now documents the full URI surface and the per-OS default.- CI workflow — mirrors the
opcua-clientpattern:unitjob cross-OS onubuntu-latest/macos-latest/windows-latest× PHP 8.2–8.5 (12 combinations);integrationjob stays Ubuntu-only (Docker-hosted OPC UA servers) withneeds: unitgating.[DOC]commits skip CI.codecov/codecov-actionbumped fromv5tov6.
[4.1.0] - 2026-04-13
Added
- ECC security policy support. The daemon and
ManagedClientnow support the 4 new Elliptic Curve Cryptography policies introduced inopcua-clientv4.1.0:SecurityPolicy::EccNistP256(NIST P-256, AES-128-CBC, SHA-256)SecurityPolicy::EccNistP384(NIST P-384, AES-256-CBC, SHA-384)SecurityPolicy::EccBrainpoolP256r1(Brainpool P-256, AES-128-CBC, SHA-256)SecurityPolicy::EccBrainpoolP384r1(Brainpool P-384, AES-256-CBC, SHA-384)- No code changes required — ECC policies work transparently via
SecurityPolicy::from()andClientBuilder. ECC certificates are auto-generated when no client certificate is provided. Username/password authentication uses theEccEncryptedSecretprotocol automatically. - ECC disclaimer: No commercial OPC UA vendor supports ECC endpoints yet. This implementation is tested exclusively against the OPC Foundation's UA-.NETStandard reference stack.
Changed
- Bumped minimum
php-opcua/opcua-clientdependency from^4.0to^4.1. - Security support expanded from 6 to 10 policies (6 RSA + 4 ECC).
- Updated CI test server suite from
php-opcua/[email protected]to@v1.1.0. - Updated documentation (README, doc/, llms.txt, llms-full.txt, llms-skills.md) to reflect ECC support and add ECC examples.
[4.0.3] - 2026-04-08
Added
- Auto-publish. When an
EventDispatcherInterfaceis provided andautoPublishis enabled, the daemon automatically callspublish()for every session that has active subscriptions. The client's existing PSR-14 event dispatch firesDataChangeReceived,EventNotificationReceived,AlarmActivated, and all other subscription events automatically — no manual publish loop required. Acknowledgements are tracked and sent internally. A self-rescheduling one-shot timer adapts to each session's minimum publishing interval. AutoPublisher— new internal class managing per-session publish cycles with self-rescheduling timers, automatic acknowledgement tracking, connection recovery, and backoff on consecutive errors (stops after 5).- Auto-connect.
SessionManagerDaemon::autoConnect(array $connections)accepts pre-configured connection definitions. On the first event loop tick after startup, the daemon connects to each endpoint, creates subscriptions, and registers monitored items and event monitored items as specified. Combined with auto-publish, this enables fully declarative monitoring — zero application code needed. CommandHandler::autoConnectSession()— opens a session to a given endpoint and creates subscriptions with monitored items in a single call. Subscription tracking (and auto-publish start) is wired automatically.- Event dispatcher injection.
CommandHandleraccepts an optionalEventDispatcherInterfaceand injects it into everyClientBuildercreated viahandleOpen(). This enables PSR-14 event delivery for all OPC UA client events in daemon-managed sessions. - Manual
publish()blocking. When auto-publish is active for a session, manualpublish()calls via IPC return anauto_publish_activeerror to prevent conflicting publish cycles. Session::getMinPublishingInterval()— returns the minimum publishing interval (in seconds) across all tracked subscriptions, used byAutoPublisherfor timer scheduling.Session::addSubscription()now accepts an optionalfloat $publishingIntervalparameter (default 500.0 ms) to track the revised publishing interval fromSubscriptionResult.CommandHandler::attemptSessionRecovery()visibility changed fromprivatetopublicto allow the daemon to pass it as a recovery callback toAutoPublisher.
Changed
SessionManagerDaemonconstructor accepts two new optional parameters:?EventDispatcherInterface $clientEventDispatcherandbool $autoPublish.CommandHandlerconstructor accepts a new optional parameter:?EventDispatcherInterface $clientEventDispatcher.trackSubscriptionChanges()now storesrevisedPublishingIntervalfromSubscriptionResultand triggersAutoPublisher::startSession()/stopSession()when a session's first subscription is created or its last subscription is deleted.cleanupExpiredSessions()andshutdown()now stop auto-publish timers before disconnecting sessions.
[4.0.2] - 2026-04-07
Changed
- Updated CI test server suite from
php-opcua/[email protected]tophp-opcua/[email protected]. - Updated all documentation references:
opcua-test-server-suite→uanetstandard-test-suite,opcua-laravel-client→laravel-opcua. - Added "Tested against the OPC UA reference implementation" disclaimer to README.
- Added "Versioning" section to README.
- Aligned Ecosystem table with
opcua-client(addedopcua-cli,opcua-client-nodeset).
[4.0.1] - 2026-03-30
Added
- Automatic session reuse. When
connect()is called with an endpoint URL and config that match an already-active session in the daemon, the existing session is returned instead of opening a new one. This eliminates accidental session duplication and makes multi-request persistence seamless — no need to manually track and pass session IDs. The reused session's inactivity timer is refreshed automatically. connectForceNew(string $endpointUrl): voidonManagedClient— forces creation of a new session even when a matching one already exists. Use this when you explicitly need parallel sessions to the same server.wasSessionReused(): boolonManagedClient— returnstrueif the lastconnect()call reused an existing daemon session instead of creating a new one.forceNewflag in the IPCopencommand — whentrue, bypasses session reuse and always creates a new session.SessionStore::findByEndpointAndConfig()— finds an existing session by endpoint URL and sanitized config.- Unit tests for session reuse: matching endpoint/config, config mismatch, endpoint mismatch,
forceNewbypass, sensitive config stripping, and touch-on-reuse.
Changed
- The IPC
opencommand response now includes'reused' => truewhen an existing session is returned.
[4.0.0] - 2026-03-26
Rebranding
- Package renamed from
php-opcua/opcua-client-session-managertophp-opcua/opcua-session-manager. - Namespace renamed from
Gianfriaur\OpcuaSessionManagertoPhpOpcua\SessionManager. All classes, tests, and configuration updated accordingly. - Dependency renamed from
gianfriaur/opcua-php-clienttophp-opcua/opcua-client. Dependency namespace changed fromGianfriaur\OpcuaPhpClienttoPhpOpcua\Client. - Repository moved to github.com/php-opcua/opcua-session-manager.
- All documentation, URLs, composer.json metadata, and code references updated to reflect the new organization.
Changed
- Breaking: Updated dependency
php-opcua/opcua-clientfrom^3.0to^4.0. - Breaking: ClientBuilder/Client split. The daemon's
CommandHandlernow usesClientBuilder::create()instead ofnew Client(). All configuration (security, timeout, cache, batching, etc.) is applied to the builder before callingconnect(), which returns aClientinstance. This mirrors the upstream v4.0.0 architecture change. No impact onManagedClientconsumers — the proxy API remains the same. - Breaking:
write()type parameter is now nullable (?BuiltinType $type = null). When omitted, the daemon's underlying client auto-detects the node's type by reading it first, then caches the result for subsequent writes. Existing code passing an explicitBuiltinTypecontinues to work unchanged. - Breaking:
writeMulti()items can now have a nullabletypefield. Whentypeis null or omitted, auto-detection is used per-node. read()now accepts a third parameterbool $refresh = false. Whentrue, the daemon bypasses the read metadata cache and forces a server read. Defaultfalsepreserves existing behaviour.- Method whitelist expanded from 37 to 45 methods to support all new v4.0.0 operations.
psr/event-dispatcher^1.0 added as dependency (interface-only package, zero runtime code).
Added
modifyMonitoredItems(int $subscriptionId, array $itemsToModify): MonitoredItemModifyResult[]— Change sampling interval, queue size, and other parameters on existing monitored items without recreating them. Proxied to the daemon's underlyingClient. ReturnsMonitoredItemModifyResult[]with revised parameters.setTriggering(int $subscriptionId, int $triggeringItemId, array $linksToAdd, array $linksToRemove): SetTriggeringResult— Configure a monitored item as a trigger for other items. Linked items are only sampled when the trigger changes. ReturnsSetTriggeringResultwith per-link status codes.- Trust store support (daemon-side). Server certificate validation can now be configured through
ManagedClient:setTrustStorePath(string)— Set the file-based trust store path. The daemon creates aFileTrustStoreinstance.setTrustPolicy(?TrustPolicy)— Set the validation level:Fingerprint,FingerprintAndExpiry, orFull. Passnullto disable.autoAccept(bool $enabled, bool $force)— Enable TOFU (Trust On First Use) for unknown server certificates.trustCertificate(string $certDer)— Manually trust a DER-encoded certificate (proxied to daemon via IPC).untrustCertificate(string $fingerprint)— Remove a certificate from the trust store (proxied to daemon via IPC).getTrustPolicy(): ?TrustPolicy— Get the current trust policy.getTrustStore(): ?TrustStoreInterface— ReturnsnullonManagedClient(trust store lives daemon-side).
- Write type auto-detection forwarding.
setAutoDetectWriteType(bool)onManagedClientconfigures whether the daemon'sClientauto-detects write types. Enabled by default. - Read metadata cache forwarding.
setReadMetadataCache(bool)onManagedClientenables caching of non-Value attributes (DisplayName, BrowseName, DataType, etc.) on the daemon'sClient. - PSR-14 Event Dispatcher interface compliance.
ManagedClientnow exposessetEventDispatcher(EventDispatcherInterface)andgetEventDispatcher(). Events are dispatched locally on theManagedClientside (daemon-side events are handled by the daemon's own dispatcher). Default:NullEventDispatcher. TypeSerializernow serializes/deserializesMonitoredItemModifyResult,SetTriggeringResult, andExtensionObjectDTOs.TypeSerializer::deserializeBuiltinType()now accepts?intand returns?BuiltinTypefor nullable write type support.TypeSerializerhandlesExtensionObjectvalues insideVariantdeserialization.- New IPC config keys in the
opencommand:trustStorePath,trustPolicy,autoAccept,autoAcceptForce,autoDetectWriteType,readMetadataCache. CommandHandlernow configuresClientBuilderwith trust store, trust policy, auto-accept, auto-detect write type, and read metadata cache settings from the IPCopencommand.
Breaking Changes
- Package name changed:
composer require php-opcua/opcua-session-manager(wasphp-opcua/opcua-client-session-manager). - Namespace changed:
PhpOpcua\SessionManager\(wasGianfriaur\OpcuaSessionManager\). write()signature changed fromwrite(NodeId|string, mixed, BuiltinType)towrite(NodeId|string, mixed, ?BuiltinType = null). The third parameter is now optional.- Dependency
php-opcua/opcua-client^4.0 required (wasphp-opcua/opcua-client^3.0).
[3.0.0] - 2026-03-23
Changed
- Breaking: Updated dependency
php-opcua/opcua-clientfrom^2.0to^3.0. - Breaking:
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). - Breaking: Strict return types for all service responses. The following methods now return typed DTOs 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,->availableSequenceNumbers)translateBrowsePaths()→BrowsePathResult[](->statusCode,->targets) withBrowsePathTarget(->targetId,->remainingPathIndex)
- Breaking: Ambiguous
$itemsparameters renamed for named parameter clarity:readMulti($readItems),writeMulti($writeItems),createMonitoredItems($subscriptionId, $monitoredItems). Only affects code using named parameters. - All
TypeSerializergetters updated to usepublic readonlyproperties fromopcua-clientv3.0.0 ($ref->nodeIdinstead of$ref->getNodeId(), etc.). TypeSerializernow preservesVariantmulti-dimensional array dimensions through serialization/deserialization roundtrips.- Method whitelist expanded from 32 to 37 methods to support all new v3.0.0 operations.
Added
- All methods accepting
NodeIdnow also acceptstring. Pass OPC UA string format directly (e.g.'i=2259','ns=2;s=MyNode'). Applies to:read,write,browse,browseAll,browseWithContinuation,browseRecursive,call(both params),historyReadRaw,historyReadProcessed,historyReadAtTime,createEventMonitoredItem,resolveNodeId($startingNodeId),invalidateCache. Also works inside arrays forreadMulti,writeMulti,createMonitoredItems,translateBrowsePaths. - 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. - PSR-3 Logging.
setLogger(LoggerInterface)/getLogger()onManagedClient. UsesNullLoggerby default. - PSR-16 Cache management.
setCache(?CacheInterface)/getCache()onManagedClient.invalidateCache(NodeId|string)andflushCache()are forwarded to the daemon's underlyingClientvia IPC. useCacheparameter added tobrowse(),browseAll(),getEndpoints(), andresolveNodeId(). Forwarded to the daemon to control cache behaviour per-call.getExtensionObjectRepository()returns a localExtensionObjectRepositoryinstance onManagedClient.discoverDataTypes(?int $namespaceIndex, bool $useCache)— forwarded to the daemon'sClientto discover and register dynamic codecs for server-defined structured types.transferSubscriptions(int[] $subscriptionIds, bool $sendInitialValues)— transfer existing subscriptions to a new session after reconnection without data loss. ReturnsTransferResult[].republish(int $subscriptionId, int $retransmitSequenceNumber)— re-request notifications that were sent but not yet acknowledged.psr/log^3.0 andpsr/simple-cache^3.0 added as dependencies.TypeSerializernow serializes/deserializes all new v3.0.0 DTO types:SubscriptionResult,MonitoredItemResult,CallResult,BrowseResultSet,PublishResult,BrowsePathResult,BrowsePathTarget,TransferResult.- Unit tests for all new DTO serialization roundtrips (SubscriptionResult, MonitoredItemResult, CallResult, BrowseResultSet, PublishResult, BrowsePathResult, TransferResult).
- Unit tests for Variant multi-dimensional dimensions preservation.
- Unit tests for
ManagedClientLogger, Cache, and ExtensionObjectRepository configuration. - Unit tests for new setter rejection (
setLogger,setCacheblocked via method whitelist). - Daemon-side PSR-3 logging. The daemon now uses a
StreamLogger(PSR-3 compliant) instead ofecho. Configure via--log-file(default: stderr) and--log-level(default: info). The same logger is injected into each OPC UAClientcreated by the daemon, so client-level events (connections, retries, errors) are captured in the daemon's log output. - Daemon-side cache configuration. The daemon's
Clientinstances now accept a configurable PSR-16 cache driver. Configure via--cache-driver(memory,file,none),--cache-path(required for file driver), and--cache-ttl(default: 300s). Browse, resolve, endpoint discovery, and type discovery results are cached inside the daemon. StreamLoggerclass (src/Logging/StreamLogger.php) — minimal PSR-3 logger that writes to a file or stream with configurable minimum log level and{placeholder}interpolation.- Automatic session recovery with subscription transfer. When a
queryoperation fails withConnectionException, the daemon automatically attempts to reconnect, transfer active subscriptions to the new session viatransferSubscriptions(), and republish unacknowledged notifications. If recovery succeeds, the original operation is retried transparently. Subscriptions that fail to transfer are removed from tracking. All recovery events are logged. Sessionnow tracks active subscription IDs.addSubscription(),removeSubscription(),getSubscriptionIds(),hasSubscriptions()methods added. Subscriptions are automatically tracked oncreateSubscriptionand untracked on successfuldeleteSubscription.- Fixed
getEndpoints()returning raw arrays instead ofEndpointDescription[]objects. - Fixed
republish()returningpublishTimeas ISO 8601 string instead of?DateTimeImmutable. - Fixed
serializeEndpointDescription()not including theserverCertificatefield.
Breaking Changes
- All service response methods listed above now return typed objects instead of arrays. Code using
$result['key']must change to$result->key. - 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.
[2.0.0] - 2026-03-20
Changed
- Breaking: Updated dependency
php-opcua/opcua-clientfrom^1.1to^2.0. - Breaking:
browse()andbrowseWithContinuation()$directionparameter changed frominttoBrowseDirectionenum. Replace raw integers (0,1) withBrowseDirection::Forward,BrowseDirection::Inverse, orBrowseDirection::Both. - Updated CI test server suite from
[email protected]tophp-opcua/[email protected]. - Method whitelist expanded from 18 to 32 methods to support all new v2.0.0 operations.
Added
- Connection state management.
isConnected(),getConnectionState(), andreconnect()are now available onManagedClient. Connection state (Disconnected,Connected,Broken) is queried from the daemon's underlyingClient. - Configurable timeout.
setTimeout(float)/getTimeout()— configure the OPC UA operation timeout (default 5s). Applied to the daemon'sClientbefore connection. - Auto-retry mechanism.
setAutoRetry(int)/getAutoRetry()— configure automatic reconnection attempts onConnectionException. Default: 0 if never connected, 1 after first successful connection. - Automatic batching.
setBatchSize(int)/getBatchSize()— transparent batching forreadMulti()andwriteMulti(). Server operation limits are auto-discovered on connect.getServerMaxNodesPerRead()/getServerMaxNodesPerWrite()query the discovered values.setBatchSize(0)disables batching. - BrowseDirection enum.
BrowseDirection::Forward,BrowseDirection::Inverse,BrowseDirection::Bothreplace raw integer direction parameters. browseAll()method. Automatically follows all continuation points and returns the complete list ofReferenceDescriptionobjects.- Recursive browsing.
browseRecursive()performs a full tree traversal with configurable max depth and cycle detection, returningBrowseNode[].setDefaultBrowseMaxDepth(int)/getDefaultBrowseMaxDepth()configure the default depth (10).-1for unlimited (hardcapped at 256). - Path resolution.
translateBrowsePaths()translates browse paths to NodeIds.resolveNodeId(string $path)resolves human-readable paths like/Objects/Server/ServerStatuswith support for namespaced segments (2:Temperature). - BrowseNode serialization.
TypeSerializernow serializes/deserializesBrowseNodetrees,BrowseDirectionenum, andConnectionStateenum. - New IPC config keys.
opencommand now acceptsopcuaTimeout,autoRetry,batchSize, anddefaultBrowseMaxDepthin theconfigpayload. - Unit tests for new types:
BrowseDirection,BrowseNode,ConnectionStateserialization roundtrips (18 new assertions). - Unit tests for
ManagedClientconfiguration: timeout, auto-retry, batching, browse depth, connection state (15 new tests). - Unit tests for setter rejection via method whitelist (
setTimeout,setAutoRetry, etc. are blocked). - Integration tests for: browse recursive (6 tests), translate browse path (4 tests), connection state (5 tests), timeout/batching/auto-retry (6 tests).
[1.1.0] - 2026-03-18
Changed
- Updated dependency
php-opcua/opcua-clientfrom^1.0to^1.1, requiring the new auto-generated certificate feature introduced in that release.
Added
- Auto-generated client certificate support. When a secure connection is opened through the daemon with
SecurityPolicyandSecurityModeconfigured but noclientCertPath/clientKeyPathprovided, the underlyingClientautomatically generates an in-memory self-signed certificate. The behaviour is transparent and inherited fromopcua-clientv1.1 — no changes required inManagedClientorCommandHandler. - Unit and integration tests for the auto-generated certificate flow.