Alpha ZealPHP is early-stage and under active development. APIs may change between minor versions until v1.0. Feedback and bug reports welcome on GitHub.

Getting Started

From a fresh machine to a running ZealPHP app — install dependencies, scaffold a project, write your first route, deploy.

Learn by Building → 14-lesson tutorial — build a Notes + AI Chat app with htmx, SQLite, and SSE streaming.
TL;DR — install in one line Ubuntu / Debian · macOS · WSL2
$ curl -fsSL https://php.zeal.ninja/install.sh | sudo bash

Installs PHP 8.3 + OpenSwoole + ext-zealphp + composer. Auto-detects your distro and bails with manual steps if it can't install for you (Fedora, Arch, Alpine, etc.). The detailed walkthrough below covers manual install, Docker, scaffolding, and deploy. Inspect the script first ↓

Your AI app without ZealPHP

Express / FastAPI server
Redis for session state
Bull / Celery for background jobs
Socket.io for WebSocket
SSE proxy middleware
Nginx reverse proxy
6 services. 6 failure points.
vs

Your AI app on ZealPHP

HTTP routes + API
WebSocket (built-in)
SSE streaming (built-in)
Task workers (built-in)
Shared memory Store (built-in)
Sessions + Timers (built-in)
1 process. php app.php

No Redis. No message queue. No sidecar. No microservice fan-out.

1. Prerequisites

PackageVersionWhy
PHP8.3+Tested on 8.3 and 8.4; OpenSwoole 26.2+ adds PHP 8.5 support
OpenSwoole22.1+Async runtime, HTTP/WebSocket server, coroutines (26.2+ for PHP 8.5)
ext-zealphp≥0.1.0Overrides header(), setcookie(), session_* at runtime (or uopz as fallback)
composer2.xDependency management
uv (optional)anyOnly for AI agent examples (Python)

2. Before you ship: known risks

ZealPHP runs as a long-lived process. This changes the rules from PHP-FPM:
  • Use $g->get / $g->session (recommended) — works in every mode, no extension needed. With ext-zealphp, $_GET / $_SESSION are also per-coroutine safe (saved/restored on every yield/resume — S1). Without ext-zealphp in coroutine mode, PHP superglobals are process-wide and would leak. See the parity rule. Also audit static variables for cross-request leaks.
  • Coroutine safety — references to RequestContext::instance() (a.k.a. $g) must not be held across yield points; each coroutine has its own context.
  • ext-zealphp function overrides are alphasession_start(), header(), etc. are intercepted via ext-zealphp (our own extension). Edge cases exist; report them.
  • Memory growth — workers stay alive between requests; profile for leaks under sustained load.
  • API stability — v0.3.x; breaking changes possible until v1.0. Pin a version in composer.json.

Report issues at GitHub Issues. Security disclosures: see SECURITY.md.

3. Install

PHP 8.3, 8.4, or 8.5. OpenSwoole 22.1+ works on PHP 8.3 and 8.4; OpenSwoole 26.2+ (released Feb 2026) added PHP 8.5 support. If you only have one PHP version available, 8.3 is the safest default.

One-line install on Ubuntu/Debian — pipes setup.sh straight from this site, no clone required:

One-line install (Ubuntu/Debian)
curl -fsSL https://php.zeal.ninja/install.sh | sudo bash
# Installs: PHP 8.3, OpenSwoole, ext-zealphp, composer
Want to inspect before piping to sudo?
curl -fsSL https://php.zeal.ninja/install.sh -o install.sh && less install.sh && sudo bash install.sh Or fetch from GitHub directly to pin a specific commit: curl -fsSL https://raw.githubusercontent.com/zealphp/zealphp/master/setup.sh | sudo bash

If you'd rather clone first (e.g. you want to send a PR):

From a cloned checkout
git clone https://github.com/sibidharan/zealphp.git
cd zealphp
sudo bash setup.sh

Or install manually:

Manual install
# 1. PHP 8.3
sudo add-apt-repository ppa:ondrej/php
sudo apt install php8.3 php8.3-cli php8.3-dev php8.3-mbstring php-pear

# 2. OpenSwoole (via PECL)
sudo pecl install openswoole
echo "extension=openswoole.so" | sudo tee /etc/php/8.3/cli/conf.d/zz-openswoole.ini
echo "short_open_tag=On" | sudo tee -a /etc/php/8.3/cli/conf.d/zz-openswoole.ini

# 3. ext-zealphp (ZealPHP's own extension — ships with the framework)
git clone --depth 1 https://github.com/sibidharan/zealphp.git /tmp/zealphp-src
cd /tmp/zealphp-src/ext/zealphp && phpize && ./configure && make && sudo make install
echo "extension=zealphp.so" | sudo tee /etc/php/8.3/cli/conf.d/50-zealphp.ini

# 4. Composer
curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer

# 5. Verify
php -m | grep -E 'openswoole|zealphp'
Why short_open_tag=On? ZealPHP templates often use <?= $var ?> for compact output. This is technically a "short echo tag" (always on in PHP 8) but enabling short_open_tag matches the recommended setup for OpenSwoole.
Docker? The framework repo includes a Dockerfile and docker-compose.yml. Run docker compose up from the cloned repo to get a fully configured container.

4. Scaffold a project

Three paths depending on what you're building:

Starter project

Clean app tree with examples. Best for new apps.

composer create-project \
  zealphp/project:^0.4.12 \
  my-app
cd my-app && php app.php

Framework repo

This very site, running locally. Read source + live demos.

git clone \
  https://github.com/sibidharan/zealphp.git
cd zealphp
composer install && php app.php

WordPress

Unmodified WordPress on OpenSwoole.

git clone \
  https://github.com/sibidharan/zealphp-wordpress.git
cd zealphp-wordpress
composer install && php app.php

How ZealPHP works — the LAMP mental model

In LAMP, Apache was the server and PHP ran inside it. In ZealPHP, OpenSwoole is the server and PHP runs inside it. Same idea, different engine.

LAMPZealPHP
Apache / NginxOpenSwoole (built into php app.php)
htdocs/about.php/about.phppublic/about.php/about
$_GET, $_POST, $_SESSIONUse $g->get / $g->post / $g->session via RequestContext::instance() — works in both modes, no extension needed. With ext-zealphp, $_GET/$_SESSION are also per-coroutine safe in both modes. See the parity rule.
session_start(), header()Same — overridden via ext-zealphp; populates the per-coroutine $g->session in coroutine mode
One process per requestOne process, thousands of concurrent coroutines
Restart Apache after config changesRestart php app.php after code changes
Needs Redis for shared stateBuilt-in Store — cross-worker shared memory
Needs Socket.io / Ratchet for WebSocketBuilt-in App::ws()

The difference: your PHP process stays alive between requests. That means persistent connections, shared memory, WebSocket, streaming — all without leaving PHP.

5. Your first page — just drop a file

Create a file in public/. It becomes a route. No framework code needed.

public/hello.php — coroutine-safe form (works in both modes)
<?php
use ZealPHP\RequestContext;

$g = RequestContext::instance();
session_start();
$g->session['visits'] = ($g->session['visits'] ?? 0) + 1;
?>
<h1>Hello from ZealPHP</h1>
<p>You've visited this page <?= $g->session['visits'] ?> time(s).</p>
<p>Query string: <?= htmlspecialchars($g->get['name'] ?? 'world') ?></p>
Two modes, one rule: always use $g->*. The default app.php runs in coroutine mode (App::mode(App::MODE_COROUTINE)) — $g->session / $g->get / $g->post are the recommended accessors (per-coroutine, always safe). With ext-zealphp, $_GET/$_SESSION are automatically per-coroutine safe in both modes (saved/restored on every yield/resume).

If you're porting a legacy .htaccess + $_* codebase, use one of the lifecycle presets before App::init(): App::mode(App::MODE_LEGACY_CGI) for unmodified WordPress/Drupal (pre-warmed subprocess pool, true per-request isolation), or App::mode(App::MODE_COROUTINE_LEGACY) to run request-style PHP concurrently with per-coroutine isolation of superglobals (S1), $GLOBALS (S2), function-local statics (S5a), and require_once re-execution (S7) (requires ext-zealphp; per-request define() isolation (S10) is a separate opt-in via App::defineIsolation(true)). The raw App::superglobals(true) flag was the legacy way these presets were configured. See Lifecycle modes and the Legacy apps page for the full migration matrix.

Start the server and visit http://localhost:8080/hello?name=PHP:

php app.php

That's it. No $app->route(), no annotations, no config files. Same for APIs — drop a file in api/:

api/device/list.php → /api/device/list
<?php
$list = function() {
    return ['devices' => ['sensor-a', 'sensor-b'], 'count' => 2];
};
This is how you migrate. Move your existing PHP files into public/. They work immediately. When you need WebSocket, streaming, or coroutines — that's when you use $app->route(). See Routing for the full picture.

6. Framework routes — when you need more

For URL parameters, WebSocket, streaming, or middleware — use programmatic routes in app.php:

app.php — minimal app
<?php
require 'vendor/autoload.php';
use ZealPHP\App;

$app = App::init('0.0.0.0', 8080);

// Return array → auto JSON
$app->route('/api/hello', function() {
    return ['message' => 'Hello from ZealPHP', 'time' => time()];
});

// URL params (Flask-style)
$app->route('/user/{id}', function($id) {
    return ['user_id' => $id];
});

// Return int → HTTP status
$app->route('/forbidden', fn() => 403);

// Streaming
$app->route('/stream', function() {
    return (function() {
        yield "<h1>Streaming</h1>\n";
        for ($i = 1; $i <= 5; $i++) {
            yield "Chunk $i<br>\n";
            usleep(200000);
        }
    })();
});

$app->run(['task_worker_num' => 0]);

Restart (Ctrl+C, then php app.php) and visit:

  • http://localhost:8080/api/hello — JSON
  • http://localhost:8080/user/42 — URL param
  • http://localhost:8080/forbidden — 403
  • http://localhost:8080/stream — streaming response

7. Deploy

ZealPHP includes built-in CLI management. For production, use the bundled systemd service:

Install as systemd service
# 1. Adjust paths in deploy/zealphp.service (WorkingDirectory, User)
sudo cp deploy/zealphp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now zealphp

# 2. Check status & logs
sudo systemctl status zealphp
journalctl -u zealphp -f

Or run standalone (without systemd):

CLI management
php app.php start -p 8080 -d   # daemonize on port 8080
php app.php status              # check if running
php app.php stop                # stop
php app.php start -w 16 -d     # 16 workers, daemonized
php app.php --help              # all flags

Verification

Confirm everything is wired up:

Smoke test
# Extensions loaded?
php -m | grep -E 'openswoole|zealphp'

# Server responds?
curl -s http://localhost:8080/ | head -5

# Composer dependencies?
composer show zealphp/zealphp
Troubleshooting
Port in use? Run php app.php stop or use -p 9000 for a different port.
Extension not loaded? Check php --ini for the config path, ensure extension=openswoole.so is in a loaded .ini.
Permission denied on port 80? Use a port above 1024, or run with setcap / behind a reverse proxy.
Learn by Building → Everything verified? Build a Notes + AI Chat app over 14 lessons — htmx, SQLite, and SSE streaming.