agentsclimarketplace

Symfony opcua

Skill php-opcua/ai-skills/skills/symfony-opcua

Symfony 7.4+ / 8.x bundle for OPC UA. Autowires `OpcuaManager` and `OpcUaClientInterface` via DI, exposes YAML-based named connections, ships an `opcua:session` console command for the session-manager daemon, and dispatches all 56 OPC UA events through Symfony's `EventDispatcherInterface`. Use this skill whenever the user is working with OPC UA from a Symfony application — controllers, Messenger handlers, console commands, scheduled tasks, EasyAdmin / API Platform endpoints, or Pest tests.From its SKILL.md

Install
npx -y skills add php-opcua/ai-skills --skill symfony-opcua

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

11.0 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it

symfony-opcua

A symfony-bundle over php-opcua/opcua-client. Three things to remember:

  1. No Facade, no static helpers. Type-hint OpcuaManager or OpcUaClientInterface in your constructor. Autowiring resolves them.
  2. OpcuaManager::connect($name) returns the underlying OpcUaClientInterface directly. Anything opcua-client can do, the client returned can do.
  3. Bundle uses AbstractBundle + DefinitionConfigurator + loadExtension() (modern Symfony 6.1+ style). No XML/YAML service definitions to ship — wiring is code-driven.

What this package is for

You want toUse
Read / write OPC UA nodes from a controller, service, commandInject OpcUaClientInterface (default conn) or OpcuaManager
Talk to multiple OPC UA serversNamed connections in YAML, $opcuaManager->connection('plc-1')
Connect to a runtime-discovered endpoint$opcuaManager->connectTo($url, $configOverrides, as: 'cache-key')
Avoid one new TCP connection per HTTP requestRun php bin/console opcua:session as a supervised daemon
React to data changes, alarms, etc. via PSR-14 → Symfony eventsConfigure auto_publish: true + per-connection auto_connect: true, register #[AsEventListener]
Test code that touches OPC UA without a serverMockClient + self::getContainer()->set(OpcUaClientInterface::class, $mock)
Stream notifications to API Platform / EasyAdmin / MercureStandard Symfony event listeners on DataChangeReceived etc.

Mental model

Controller/Service
   └── $opcuaManager (autowired)
       └── ->connection($name)
           ├── shouldUseSessionManager() == true?
           │   └── ManagedClient (IPC → daemon → TCP → server)
           │       └── TransportFactory picks UnixSocketTransport (Linux/macOS) or TcpLoopbackTransport (Windows)
           └── shouldUseSessionManager() == false?
               └── ClientBuilder::create()->...->connect()  (direct TCP, new connection per call)

Both branches expose the same OpcUaClientInterface. Your code does not know which it has.

Quick start

composer require php-opcua/symfony-opcua

# If Flex is not enabled, register in config/bundles.php:
#   PhpOpcua\SymfonyOpcua\PhpOpcuaSymfonyOpcuaBundle::class => ['all' => true],

cp vendor/php-opcua/symfony-opcua/config/opcua.yaml config/packages/php_opcua_symfony_opcua.yaml
# .env
OPCUA_ENDPOINT=opc.tcp://plc.example:4840
OPCUA_USERNAME=operator
OPCUA_PASSWORD=changeme
OPCUA_AUTH_TOKEN=long-random-secret
namespace App\Controller;

use PhpOpcua\Client\OpcUaClientInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;

class PlcController extends AbstractController
{
    public function __construct(
        private readonly OpcUaClientInterface $opcua,
    ) {}

    #[Route('/server-state', methods: ['GET'])]
    public function state(): JsonResponse
    {
        $state = $this->opcua->read('i=2259')->getValue();
        return $this->json(['state' => $state, 'running' => $state === 0]);
    }
}

The 3 patterns you will use 90% of the time

Pattern A — one-shot read/write (no daemon)

Best for HTTP requests, console commands, Messenger handlers. The bundle opens a TCP connection per call.

public function showServerState(OpcUaClientInterface $opcua): array
{
    $state = $opcua->read('i=2259')->getValue();
    return ['state' => $state, 'running' => $state === 0];
}

Pattern B — daemon-backed, transparent session reuse

When you run php bin/console opcua:session under systemd/supervisor, every client call goes through the daemon. Sessions are reused.

php bin/console opcua:session --timeout=600 --max-sessions=100

Application code is unchanged. Same $opcua->read(...), now backed by ManagedClient automatically.

Pattern C — auto_publish + Symfony event listeners

# config/packages/php_opcua_symfony_opcua.yaml
php_opcua_symfony_opcua:
    session_manager:
        auto_publish: true
    connections:
        plc-1:
            endpoint: '%env(PLC1_ENDPOINT)%'
            auto_connect: true
            subscriptions:
                - publishing_interval: 500.0
                  monitored_items:
                      - { node_id: 'ns=2;s=Temperature', client_handle: 1 }
namespace App\EventListener;

use PhpOpcua\Client\Event\DataChangeReceived;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener]
final class SensorReadingListener
{
    public function __invoke(DataChangeReceived $event): void
    {
        // Persist, broadcast, alert, etc.
    }
}

Service-container surface

The bundle registers (all public so they show up in debug:container):

ServiceClassWhat it is
PhpOpcua\SymfonyOpcua\OpcuaManagerOpcuaManagerThe connection manager
opcuaalias of OpcuaManagerBackwards-compat alias
PhpOpcua\Client\OpcUaClientInterfacefactory: $opcuaManager->connection(null)Default connection, autowire target
opcua.command.sessionSessionCommandThe console command (tagged console.command)
php bin/console debug:container --tag=console.command | grep opcua
php bin/console debug:autowiring OpcUaClient

Available console commands

CommandPurpose
opcua:sessionStart the session-manager daemon. Options: --timeout, --cleanup-interval, --max-sessions, --socket-mode. Other settings (log channel, cache pool, auth token, cert dirs, auto-publish) come from session_manager YAML config.

The command is registered automatically when the bundle is loaded.

Inherited methods (v4.4.0)

The bundle is a thin wrapper — every method on OpcUaClientInterface works through $opcuaManager->connect(). v4.4.0 added 21 new methods (Part 11 §6.9 HistoryUpdate, Part 5 §C.2/C.3 File Transfer, Part 13 Aggregates). They are reachable as ordinary client calls:

// HistoryUpdate
$opcua->historyInsertData('ns=2;s=Backfill', $dataValues);

// File transfer
$handle = $opcua->openFile($fileNode, OpenFileMode::Read);
$bytes  = $opcua->readFile($fileNode, $handle, 65536);
$opcua->closeFile($fileNode, $handle);

// Aggregates
$bucketed = $opcua->historyAggregate('ns=2;s=Temp', $start, $end, 60000.0, AggregateFunction::Average);

When to follow the references

Progressive disclosure — only load what the task needs:

  • references/CONFIG.md — every YAML key, env vars, named connections, defaults, ServiceLocator wiring for cache pool / log channel
  • references/SESSION_MANAGER.md — daemon command, systemd + supervisor configs, IPC endpoints, auto-publish lifecycle, mixed-version upgrade
  • references/EVENTS.md — all 56 PSR-14 events, payload shapes, #[AsEventListener] patterns, Messenger-friendly handlers
  • references/INTEGRATIONS.md — Messenger, API Platform, EasyAdmin, Mercure, FrankenPHP/Octane-style worker mode, Doctrine, Monolog channels
  • references/SECURITY.md — policies, modes, trust store, cert auto-generation, X.509 user auth, env-driven config
  • references/TESTING.md — Pest setup, MockClient in test container, self::getContainer()->set(...), KernelTestCase patterns
  • references/PITFALLS.md — common gotchas: services as request-scoped, daemon vs worker, mixed daemon versions
  • assets/recipes.md — copy-pasteable end-to-end recipes for the most common tasks

Idiomatic patterns

  1. Type-hint OpcUaClientInterface for the default connection. Type-hint OpcuaManager when you need to switch between named connections.
  2. YAML defines wiring, env defines secrets. %env(OPCUA_PASSWORD)% in YAML; OPCUA_PASSWORD=... in .env.local.
  3. Use named connections per server. Don't string-build endpoints in code. Define plc-1, plc-2, historian in YAML.
  4. Don't disconnect in HTTP requests when the daemon is enabled. Let the session manager handle lifecycle.
  5. For auto-published subscriptions, never call publish() yourself. Returns auto_publish_active error.
  6. Bind Messenger handlers to a dedicated transport for OPC UA event work so a slow PLC doesn't back-pressure the main queue.
  7. Use a dedicated Monolog channel opcua. Configure log_channel: opcua in session_manager. Keeps OPC UA noise out of the main log.
  8. Run the daemon under a dedicated UID with socket_mode: 0600. The web user must be in the daemon's group (or share UID).
  9. In FrankenPHP / Swoole, the bundle's OpcuaManager is already long-lived per worker — just enable the daemon for transparent session reuse across requests.
  10. Override OpcUaClientInterface in test containers for hermetic tests; bypass the daemon entirely.

Exit codes (opcua:session)

CodeMeaning
0Daemon exited cleanly (SIGTERM/SIGINT)
1Configuration error (invalid socket_path, missing required key)
2Bind failure (port in use, socket-path EACCES, parent dir missing)
3Runtime error inside daemon loop (logged via Monolog channel)

Non-zero exits should trigger Restart=on-failure in systemd / autorestart=true in supervisor.

Versioning

The Symfony bundle versions lock-step with php-opcua/opcua-client and php-opcua/opcua-session-manager. Upgrade order:

  1. Daemon first. Stop opcua:session, composer update, restart.
  2. Application second. composer update php-opcua/symfony-opcua.

Reverse order → BadMethodCallException when a v4.4 application calls a v4.4-only method against a v4.3 daemon.

What this skill does NOT cover

  • The raw OPC UA protocol — see the opcua-client skill.
  • The session-manager daemon's IPC protocol — see the opcua-session-manager skill.
  • CLI usage — see the opcua-cli skill.
  • Companion-spec types (DI, IA, AutoID, etc.) — see the opcua-client-nodeset skill.
  • Laravel patterns — see the laravel-opcua skill (mirror of this one for Laravel).

What ships with it: 8 files

81.7 KB alongside SKILL.md

assets/

Keep looking

Skills are one crate of 326,506. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.