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