symfony-opcua · v4.3.x
Docs · Observability

Debugging

When something doesn't work — connection refused, write rejected, events silent. A Symfony-shaped decision tree from console commands to netcat.

Symptom → cheapest probe → next step.

"I can't read anything"

1. Confirm the bundle is wired

bash terminal
php bin/console debug:autowiring opcua
php bin/console debug:config php_opcua_symfony_opcua | head -40
Symptom Cause
debug:autowiring shows nothing Bundle missing from config/bundles.php
debug:config says "no config" Missing config/packages/php_opcua_symfony_opcua.yaml
Wrong endpoint Stale env — bin/console cache:clear
OpcUaClientInterface not autowired YAML config doesn't define a default connection

2. Try a raw read

php quick probe
// In a controller or with bin/console debug:container:
$dv = $this->opcua->connect()->read('i=2256');   // ServerStatus_State

i=2256 always exists on a working OPC UA server. If this fails but ns=2;s=Speed works, the issue is the node ID. If this fails too, the connection itself is broken.

3. Check connection logs

bash recent logs
tail -50 var/log/opcua-$(date +%Y-%m-%d).log
# or
sudo journalctl -u opcua-session-manager -n 50

Look for ConnectionFailed events — the reason field has the next step.

"Writes are rejected"

Read the node's attributes:

php attribute check
use PhpOpcua\Client\Types\AttributeId;

$access = $client
    ->read('ns=2;s=Setpoint', AttributeId::AccessLevel)
    ->getValue();   // bit 1 must be set for write

$dataType = $client
    ->read('ns=2;s=Setpoint', AttributeId::DataType)
    ->getValue();   // what type does the server expect?
Status Likely cause
Bad_TypeMismatch PHP type → BuiltinType mismatch — use explicit type
Bad_NotWritable Node isn't writable
Bad_UserAccessDenied Session lacks permission
Bad_OutOfRange Value too high/low

See Operations · Writing.

"Events stopped firing"

1. Daemon alive?

bash probe daemon
echo '{"id":1,"t":"req","method":"ping","args":[]}' \
    | nc -U /var/run/opcua/sessions.sock

If nc hangs → daemon not listening. If sessions count is 0 → no sessions held.

2. Auto-publish on?

bash config check
php bin/console debug:config php_opcua_symfony_opcua | grep auto_publish

If false, the daemon doesn't emit events even though subscriptions exist.

3. Listeners registered?

bash listener check
php bin/console debug:event-dispatcher "PhpOpcua\Client\Event\DataChangeReceived"

Expected output:

text expected
Listeners for "PhpOpcua\Client\Event\DataChangeReceived" event
========================================================================

 ------- -------------------------------- --------------------------
  Order   Callable                         Priority
 ------- -------------------------------- --------------------------
  #1      App\EventListener\StoreSpeed     0

If listeners aren't listed, check the #[AsEventListener] attribute is on the method or the class.

4. Messenger worker running?

bash worker check
systemctl status messenger-worker
# or with Supervisor
sudo supervisorctl status messenger-worker

php bin/console messenger:stats async_opcua

If the queue is growing, the worker is down. If failed messages are accumulating, check messenger:failed:show.

"Works in bin/console, not in HTTP"

Likely causes:

  1. Different connection. Tinker / commands use the default; the request uses a different one. Check $opcua->connect(...).
  2. Container cache. Run php bin/console cache:clear and try again.
  3. FrankenPHP / Octane stale state. Restart workers.

"Tests fail with connection errors"

Tests should not hit a real OPC UA server. Disable in .env.test:

bash .env.test
OPCUA_SESSION_MANAGER_ENABLED=false

…with the bundle config pulling that env. See Testing · PHPUnit and Pest setup.

"ServiceException: Bad_SessionIdInvalid after a while"

Server's MaxSessionTimeout fired. The bundle's client reopens on the next call. If frequent in managed mode:

  1. The daemon's session_manager.timeout (the bundle config key — session_manager.timeout, not session_timeout) should be less than the server's MaxSessionTimeout (clean teardown before the server times out).
  2. The daemon's cleanup loop should run — check logs for periodic cleanup entries.

"Cert handshake fails"

Error Likely cause
UntrustedCertificateException Server cert not in trust store
SecurityException — expired Server cert past NotAfter
SecurityException — hostname mismatch URL host doesn't match cert's SAN
HandshakeException / SecurityException Server doesn't offer the requested policy

Inspect server cert (against the bundle's cached fingerprint):

bash inspect
openssl s_client -connect plc.factory.local:4840 -showcerts < /dev/null

See Security · Trust store.

"Memory leak over time"

In long-running workers (Messenger, FrankenPHP), this happens. Sources:

  1. Cache without LRU. Use Redis with allkeys-lru.
  2. Doctrine entity growth in event listeners. Don't accumulate in static state; call $em->clear() periodically inside batched handlers.
  3. Builder reuse. Each readMulti(null) call returns a fresh builder instance — don't accumulate state across iterations.

Bound workers with --memory-limit=512.

Symfony Profiler

APP_DEBUG=1 plus a controller hit shows the WebProfilerBundle toolbar. The OPC UA bundle doesn't ship a data collector yet, but you can add a small one — see Profiler and data collectors.

For now, the Logs tab shows OPC UA log lines from the channel you configured.

See the wire

For protocol-level debugging:

bash tcpdump
sudo tcpdump -i any -A 'port 4840' -w opcua.pcap

Open in Wireshark with the OPC UA dissector enabled (bundled since Wireshark ≥ 3.0).

Intermittent failures

When timing is unclear:

  1. Increase logging — set OPCUA_LOG_LEVEL=debug temporarily.
  2. Watch the daemon's session count — sudden drops indicate server-side teardown.
  3. Add a scheduled app:opcua:ping command that records ping latency over time.

Failure escalation

When the failure is in the OPC UA library, capture:

  1. Exception class + full message.
  2. Connection config (redacted of secrets).
  3. Relevant log lines at debug level.
  4. Server product/version from Server.ServerStatus.BuildInfo.

File against upstream: github.com/php-opcua/opcua-client/issues or github.com/php-opcua/opcua-session-manager/issues.

Documentation