Quick start
Four idiomatic Symfony shapes — controller, console command, Messenger handler, scheduled task — each exercising the bundle differently.
Four shapes a typical Symfony app reaches for first. Each one covers a different runtime context.
Assumed .env:
OPCUA_ENDPOINT=opc.tcp://127.0.0.1:4840
Assumed config/packages/php_opcua_symfony_opcua.yaml:
php_opcua_symfony_opcua:
connections:
default:
endpoint: '%env(OPCUA_ENDPOINT)%'
1. HTTP controller
The most common entry point. OpcuaManager is autowired into
the constructor.
namespace App\Controller;
use PhpOpcua\SymfonyOpcua\OpcuaManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
class SpeedController extends AbstractController
{
public function __construct(private readonly OpcuaManager $opcua) {}
#[Route('/api/plc/speed', methods: ['GET'])]
public function show(): JsonResponse
{
$dv = $this->opcua->connect()->read('ns=2;s=Speed');
return $this->json([
'value' => $dv->getValue(),
'good' => $dv->statusCode === 0,
'at' => $dv->sourceTimestamp?->format('c'),
]);
}
}
Hit it:
curl http://localhost:8000/api/plc/speed
2. Console command
For one-shot scripts, scheduled tasks, or admin tooling.
namespace App\Command;
use PhpOpcua\SymfonyOpcua\OpcuaManager;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'app:check-plc', description: 'Probe the PLC and print the Server state')]
class CheckPlcCommand extends Command
{
public function __construct(private readonly OpcuaManager $opcua)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$client = $this->opcua->connect();
$state = $client->read('i=2259')->getValue(); // Server_ServerStatus_State
$io->success("PLC reports state: {$state} (0 = Running)");
return Command::SUCCESS;
}
}
Run:
php bin/console app:check-plc
Pair with useConsoleLogger() for -v / -vv / -vvv to
flow into the OPC UA client's logger — see
Observability · Logging.
3. Messenger handler
For work that shouldn't block a request. The message holds the
input; the handler resolves OpcuaManager from the
container.
namespace App\Message;
final readonly class SamplePlc
{
public function __construct(public string $nodeId) {}
}
namespace App\MessageHandler;
use App\Message\SamplePlc;
use App\Repository\PlcReadingRepository;
use PhpOpcua\SymfonyOpcua\OpcuaManager;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
final class SamplePlcHandler
{
public function __construct(
private readonly OpcuaManager $opcua,
private readonly PlcReadingRepository $repo,
) {}
public function __invoke(SamplePlc $message): void
{
$dv = $this->opcua->connect()->read($message->nodeId);
$this->repo->save([
'node_id' => $message->nodeId,
'value' => $dv->getValue(),
'good' => $dv->statusCode === 0,
'at' => $dv->sourceTimestamp ?? new \DateTimeImmutable(),
]);
}
}
Dispatch from anywhere:
use App\Message\SamplePlc;
use Symfony\Component\Messenger\MessageBusInterface;
$bus->dispatch(new SamplePlc('ns=2;s=Speed'));
See Integrations · Messenger for async transports, retries, and the deeper async pattern.
4. Scheduled task
Run the command on a cron-like schedule using Symfony Scheduler:
namespace App\Scheduler;
use App\Message\SamplePlc;
use Symfony\Component\Scheduler\Attribute\AsSchedule;
use Symfony\Component\Scheduler\RecurringMessage;
use Symfony\Component\Scheduler\Schedule;
use Symfony\Component\Scheduler\ScheduleProviderInterface;
#[AsSchedule('plc')]
final class PlcSchedule implements ScheduleProviderInterface
{
public function getSchedule(): Schedule
{
return (new Schedule())
->add(RecurringMessage::every('1 minute', new SamplePlc('ns=2;s=Speed')))
->add(RecurringMessage::every('5 minutes', new SamplePlc('ns=2;s=Temperature')));
}
}
Run the scheduler worker:
php bin/console messenger:consume scheduler_plc
Every minute a SamplePlc message is dispatched and the handler
above picks it up. See Console and scheduler.
Two-line write
For completeness — writing is just as terse:
$this->opcua->connect()->write('ns=2;s=Setpoint', 75.0);
The package auto-detects the OPC UA type from the PHP value. See Operations · Writing for the explicit-type path.
A subscription
A subscription delivers value changes via the EventDispatcher (in managed mode with auto-publish enabled — see Session manager · Auto-publish).
namespace App\EventListener;
use PhpOpcua\Client\Event\DataChangeReceived;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
final class StoreSpeedReading
{
private const SPEED_HANDLE = 1;
#[AsEventListener]
public function __invoke(DataChangeReceived $event): void
{
if ($event->clientHandle === self::SPEED_HANDLE) {
// ... persist
}
}
}
See Events · Overview.
Where to read next
- How symfony-opcua fits — the architecture diagram.
- Manager vs interface — picking the right injection type.
- Operations · Reading — read patterns and error handling.