NativePHP — but for Symfony

Build desktop apps
with Symfony.

Write standard Symfony controllers, services, and events. Ship a .exe, .dmg, or .AppImage — powered by Electron or Tauri.

Get started → composer require toxicity/symfony-native-bridge
🐘 PHP ≥ 8.2 🎵 Symfony ^6.4 / ^7 / ^8 ⚡ Electron 🦀 Tauri 📄 MIT

Architecture

How it works

A three-layer bridge between your PHP backend and the native OS runtime.

Your Symfony Application
Controllers · Twig · API Platform · Services · Events
HTTP loopback · 127.0.0.1:8765
SymfonyNativeBridgeBundle
WindowManager TrayManager NotificationManager DialogManager StorageManager AppManager ClipboardManager ProtocolManager
WebSocket IPC (Electron) · Named pipe (Tauri)
Electron / Tauri Runtime
BrowserWindow · Tray · Notifications · Dialog · autoUpdater · store
01

native:serve starts PHP on 127.0.0.1:8765

02

Spawns Electron or Tauri with a BrowserWindow at that URL

03

PHP and runtime talk over WebSocket IPC or named pipe

04

PHP calls driver->call('window.open', …) — native handles it

05

Native events pushed back as real Symfony events. No polling.

Services

Everything you need

All services are autowirable. Just type-hint in your constructor.

🪟

WindowManager

Open, close, and control native windows from your controllers. Set dimensions, titles, and behavior programmatically.

📋

TrayManager

Create system tray icons with dynamic context menus. Supports separators, roles, and click events.

🔔

NotificationManager

Send native OS notifications in one line. React to clicks with NotificationClickedEvent.

💬

DialogManager

File pickers, confirm dialogs, save dialogs — native OS dialogs with full filter support.

💾

StorageManager

Persistent key-value store across sessions. Includes a remember() memoization helper.

⚙️

AppManager

Get app paths, open external URLs, reveal files in Finder / Explorer, check for updates, quit.

📋

ClipboardManager

Read and write text or images to the system clipboard. Check presence with hasText() / hasImage().

🔗

ProtocolManager

Register custom URL schemes (myapp://) for deep-linking. Receive DeepLinkReceivedEvent with parsed URL parts.

Drivers

Electron or Tauri — your call

Both implement the same NativeDriverInterface. One config line to swap.

Electron
driver: electron
  • No Rust required
  • Node.js ≥ 18
  • WebSocket IPC
  • Largest ecosystem
🦀 Tauri
driver: tauri
  • Rust + Cargo required
  • Node.js ≥ 18
  • Named pipe IPC
  • Smaller binary size
config/packages/symfony_native_bridge.yaml
# Switch with one line
symfony_native_bridge:
    driver: tauri   # was: electron

    app:
        name:       "My App"
        version:    "1.0.0"
        identifier: "com.example.my-app"

    window:
        width:  1280
        height: 800

    build:
        output_dir: dist
        targets:    [windows, macos, linux]

Usage

Symfony all the way down

Write code you already know. The bridge handles the rest.

bash
# 1. Install the package
composer require toxicity/symfony-native-bridge

# 2. Register the bundle in config/bundles.php
SymfonyNativeBridge\SymfonyNativeBridgeBundle::class => ['all' => true],

# 3. Install the native runtime
php bin/console native:install               # Electron (default)
php bin/console native:install --driver=tauri  # Tauri

# 4. Start developing
php bin/console native:serve

# 5. Build for distribution
php bin/console native:build --target=windows --target=macos --target=linux

Events

Native events as Symfony events

The runtime pushes events back to PHP. Real Symfony dispatching — no polling, no hacks.

Event classConstantTriggered when
AppReadyEventnative.app.readyRuntime is up and first window is open
AppBeforeQuitEventnative.app.before_quitUser tries to quit — call $event->prevent() to cancel
AppActivatedEventnative.app.activatedmacOS: app clicked in Dock
WindowFocusedEventnative.window.focusedA window gains focus
WindowBlurredEventnative.window.blurredA window loses focus
WindowClosedEventnative.window.closedA window is closed
TrayClickedEventnative.tray.clickedTray icon clicked (left / right / double)
TrayMenuItemClickedEventnative.tray.menu_item_clickedA tray menu item is clicked
WindowResizedEventnative.window.resizedA window is resized — carries new width & height
WindowMovedEventnative.window.movedA window is moved — carries new x & y position
DeepLinkReceivedEventnative.protocol.deep_linkCustom URL scheme opened — exposes scheme, host, path, query
UpdateAvailableEventnative.updater.update_availableA new version is found
NotificationClickedEventnative.notification.clickedUser clicks a notification

Roadmap

What's coming

Hot-reload in dev mode
Instant refresh on PHP file changes
#[NativeRoute] attribute
URL-less window routing from PHP
Deep-link & protocol handler
Register custom URL schemes for your app
Clipboard API service
Read and write the system clipboard from PHP
Symfony Messenger transport
Async native calls via message bus
macOS Menu Bar mode
Pure menu bar app — no Dock icon

Ship it. Natively.

Your Symfony skills transfer 100%. No new framework to learn.

View on GitHub composer require toxicity/symfony-native-bridge