Skip to content

Getting started

From an empty directory to a desktop application, in plain PHP. With a framework the steps are the same, with less code: Symfony and Laravel say what changes.

You need the nativella command: see Installation.

1. A PHP application

Nativella serves anything with a public/index.php:

mkdir -p hello/public
cd hello
public/index.php
<?php

$name = htmlspecialchars($_GET['name'] ?? 'world');
?>
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Hello</title>
<h1>Hello, <?= $name ?>!</h1>
<form>
    <input name="name" placeholder="Your name" autofocus>
    <button>Greet me</button>
</form>

Run it:

nativella run .

A window opens on the page. Submit the form and $_GET is filled, as behind any web server, except that no server started. ss -ltnp shows no new port, because the request went from the webview to PHP inside the nativella process.

Reloading

When you change index.php and save, the window reloads. nativella run watches the project files, and reloads every window once they stop changing, with the new code, as opcache checks the files on every request. It leaves out what the application and its tools write to, var/, storage/, bootstrap/cache/, vendor/, node_modules/ and .git/, the data directory, the logs and SQLite databases an application writes anywhere (*.log, *.sqlite, *.db and their journals), and the files editors keep aside (.index.php.swp, index.php~). A standalone executable does not watch anything. NATIVELLA_RELOAD=0 turns it off.

2. Give it a name

The window is titled after the directory. Name the application in composer.json:

composer.json
{
    "name": "acme/hello",
    "extra": {
        "nativella": {
            "name": "Hello",
            "identifier": "com.acme.hello",
            "window": { "width": 520, "height": 360, "resizable": false }
        }
    }
}

The identifier names the directory where the application keeps its data, so pick it once. Configuration lists every key.

3. Call native code

Install the PHP package:

composer require nativella/nativella

Then call it from the page:

public/index.php
<?php

use Nativella\Nativella;

require __DIR__.'/../vendor/autoload.php';

$native = Nativella::create();

if ('POST' === $_SERVER['REQUEST_METHOD']) {
    $native->notification->send('Hello', 'From PHP, through a desktop notification.');

    header('Location: /', true, 303);

    return;
}
?>
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Hello</title>
<form method="post">
    <button>Notify me</button>
</form>

When you click the button, a notification shows up and the page comes back. The 303 redirect works although WebKit follows no redirect for a custom scheme, because the runtime follows it for the page. How it works has the details.

4. React to native events

Step 3 only calls native code. When something must happen the other way round (the application started, a menu item was clicked, files were dropped on a window, a timer fired), PHP has to hear about it through a native event. An application that does not react to any can skip this step. With Symfony and Laravel, a listener does what the code below does by hand.

Native events reach PHP as requests to POST /_nativella/event, which only the runtime can send. EventFactory::fromGlobals() turns such a request into an event object, and returns null for every other request. Put it at the top of the front controller:

public/index.php
<?php

use Nativella\Event\AppBooted;
use Nativella\Event\EventFactory;
use Nativella\Event\MenuItemClicked;
use Nativella\Menu\MenuItem;
use Nativella\Menu\Role;
use Nativella\Nativella;

require __DIR__.'/../vendor/autoload.php';

$native = Nativella::create();

if (null !== $event = EventFactory::fromGlobals()) {
    if ($event instanceof AppBooted) {
        $native->menu->set(
            MenuItem::submenu(
                'File',
                MenuItem::action('hello', 'Say hello', 'CmdOrCtrl+H'),
                MenuItem::separator(),
                MenuItem::role(Role::Quit),
            ),
            MenuItem::editMenu(),
        );
    } elseif ($event instanceof MenuItemClicked && 'hello' === $event->id) {
        $native->dialog->message('Hello from the menu!');
    }

    http_response_code(204);

    return;
}

// The pages of the application, as before.

AppBooted comes first, before any page is served. It is where an application sets its menus, opens its windows and migrates its database. Lifecycle and events lists every event.

5. Ship it

composer install --no-dev --optimize-autoloader
cd ..
nativella build hello
./dist/hello

dist/hello is one executable carrying the whole project; dist/ itself is never packed, so building from the project directory works too. On Linux, nativella deb hello makes a Debian package of it. Building a standalone app says what goes in, and what the machine still needs.

Where to go next

  • What runs where: which native API works on computers, which on phones.
  • Windows, Menus, Dialogs: the native APIs, one page each.
  • Mobile: the same project as an Android or iOS application, with nativella android and nativella ios.
  • Database: keep data in SQLite, in the application's data directory.
  • Pages and JavaScript: native events in the browser, and context menus.
  • The repository's demos: Notes with Symfony, Tasks with Laravel, Snippets in plain PHP.