Getting started

A MyPHP app shows its windows with a web frontend, in the system's webview,

or with native UI that MyPHP draws itself, written in PHP.

myphp init starts a project of either kind, and an app can add windows of

the other kind later.

Install

Install PHP 8.3 or later with ext-ffi (ffi.enable=1 for scripts run

outside myphp dev). A project with a web frontend also needs

Bun, which runs its tools, and apps with web pages need

the system's webview: on Linux, GTK 3 and WebKitGTK 4.1, e.g. `sudo apt

install libwebkit2gtk-4.1-0` on Debian and Ubuntu.

Native UI needs nothing more: it runs on what the system has (GTK 3 on

Linux).

The myphp command line tool creates, runs and packages apps. It is a PHP

script, bin/myphp in a checkout of MyPHP:

bin/myphp init my-app

Projects with a web frontend also depend on it through npm, as the

myphp-cli package, which their scripts run. See the CLI.

myphp doctor checks that the machine has what MyPHP needs.

Create a project

A web frontend

myphp init -template web my-app creates the PHP app and a TypeScript

frontend built with Vite, side by side, and installs

their dependencies:

my-app/
├── app.php           the app: its windows and the PHP code the page calls
├── boot.php          loads MyPHP
├── myphp.config.ts   the app's name, identifier and version, and how to build it
├── package.json      the scripts, and the frontend's dependencies
├── index.html        the page
├── src/
│   ├── main.ts       the page's code
│   ├── style.css
│   └── myphp.ts      the typed client of the PHP code, generated
├── vite.config.ts
├── tsconfig.json
└── resources/
    └── icon.png      the app icon, a 1024×1024 PNG

Builds go to dist/ (the frontend) and build/ (the packaged apps), and

the development app to .myphp/; .gitignore leaves them out. See

configuration for the fields of myphp.config.ts.

Native UI

myphp init my-app (or -template native) creates an app whose window

shows native UI: PHP alone, with no frontend and no Bun.

my-app/
├── app.php          the app: its state, its view and its window
├── boot.php         loads MyPHP
├── myphp.json       the app's name, identifier and version
└── resources/
    └── icon.png     the app icon, a 1024×1024 PNG

boot.php loads MyPHP from the checkout that framework in the

configuration names, or from MYPHP_HOME, which myphp dev and `myphp

generate` set, and from the copy inside the app once it is built.

Develop

cd my-app
bun run dev        # a web frontend: runs myphp dev
myphp dev          # native UI

myphp dev builds a development version of the app and starts it, then

rebuilds and restarts it when you edit a .php file, the configuration,

the icon or a resource. A build that fails keeps the previous one running.

Quit the app, or press Ctrl+C, to stop.

With a web frontend, it also writes src/myphp.ts and starts the Vite dev

server, from which the development app loads its pages: edit src/main.ts

or src/style.css and Vite updates the page right away. Development builds

have the web inspector: right-click the page and choose Inspect Element,

or call $win->page()->openDevTools().

On macOS the development app is a real app bundle, My App Dev with the

identifier of the app plus .dev, so that it keeps its data, preferences

and permissions apart from the installed app.

Call PHP

From the page

app.php binds a PHP object, whose public methods the page can call:

<!-- no-test -->

/** Greeter is callable from the frontend: `myphp generate` turns its methods into typed TypeScript functions in src/myphp.ts. */
final class Greeter
{
    /** Greet returns a greeting for name. */
    public function greet(string $name): string
    {
        return 'Hello, ' . ($name === '' ? 'stranger' : $name) . '! This message comes from PHP.';
    }
}

/** Tick is sent to the page every second: the time, in milliseconds since 1970. */
$tick = new Event('tick', 'int');

Ipc::bind(new Greeter());

src/myphp.ts, which myphp dev keeps up to date, turns them into typed

functions and events:

import { Greeter, events } from "./myphp";

const greeting = await Greeter.greet("world"); // Promise<string>
events.tick.on((time) => console.log(time)); // time: number

app.php calls App::setName() first, before it makes its services:

App::path() names the app's data folder (PathName::UserData, …) after

the name, and keeps the folder it found, so a service made before the name

was set, as new Notes(App::path(PathName::UserData) . '/notes.json'),

would keep its data under the wrong folder.

Add a method to Greeter, save, and it is there to call once the app has

restarted. Calling PHP from the frontend covers what methods

can take and return, errors, events and security.

From native UI

Native UI is PHP, so it calls your code directly, with no bindings. The

view is a function of your app's state that MyPHP calls to build each

frame; it asks its elements what happened since the last one:

$app = new class {
    public TextState $name;
    public string $greeting = '';
    public function __construct() { $this->name = new TextState(); }
};

$view = function (Context $c) use ($app) {
    $c->column(0, 16, 1)->fill()->alignItems(Node::CENTER)->justify(Node::CENTER);
      $c->input($app->name, 'Your name', 240);
      if ($c->clicked($c->primaryButton('Greet'))) {
          $app->greeting = 'Hello, ' . $app->name->text . '!'; // your PHP code
      }
      $c->text($app->greeting);
    $c->end();
};

tests/Headless.php clicks and types into a view without a window, as

fast as a unit test (see testing). [Native

UI](ui/README.md) covers layout, widgets, text, input, drawing and

changing the state from other work.

Build

bun run build        # a web frontend: runs myphp build
myphp build          # native UI

myphp build makes the app, with the frontend built by Vite in it when

there is one, and packages it for the machine's platform in

build/<os>-<arch>/ (the templates set out to build):

| Platform | Output |

|---|---|

| macOS | My App.app, and a disk image My App 0.1.0 arm64.dmg |

| Linux | the launcher my-app with its desktop entry and icon, their archive, a .deb package and a PKGBUILD |

myphp build -platform darwin/universal

The apps run where they are, but to ship them to users, sign them: see

Building and distributing.

Next steps

the frontend, for web pages.

  • Menus and the tray and the desktop APIs.
  • The examples in the repository, which ./run.sh <name> runs:
  • with web pages: examples/hello (the smallest app), examples/todo

(typed services and events, persistence, dialogs, menus, several

windows), examples/frameless (a custom title bar) and

examples/native (menus, a tray icon, dialogs, notifications, a

global shortcut, the clipboard and dark mode);

  • with native UI: examples/counter-native (a counter),

examples/clipboard (copying and dragging text, HTML, lazy JSON, and

file lists), examples/effort-slider, examples/confetti,

examples/blackhole and examples/chat-glow (shaders and motion),

examples/timeline (an intro on one playhead) and examples/gallery

(a tour of the toolkit: layout, widgets, text editing, a long list,

drawing and overlays).