Compile PHP to a native iOS Swift Package — a SwiftPM wrapper that embeds a static PHP runtime to enable “serverless” iPhone/iPad apps.
A Swift Package that vendors a statically-linked PHP runtime (arm64-apple-ios) and a tiny bridge so your SwiftUI / UIKit apps can execute bundled PHP scripts fully offline—no servers, no sockets. Great for on-device templating, data transforms, DSLs, migrations, or porting existing PHP logic to iOS.
- 📦 Zero server: Run PHP in-process on device (no network).
- 🧱 Static runtime: Prebuilt PHP with common extensions (json, pcre, mbstring, tokenizer, xml, dom, libzip).
- 🧩 Swift bridge:
PhpEngineAPI forphp -r, script files, argv/env, and JSON IO. - 🔐 App-Store friendly: No downloading executable code; all scripts are bundled.
- 🗂️ Resources support: Ship
.phpfiles via SPM resources. - 🧪 Deterministic: Sandboxed FS with per-app temp and config dirs.
- ⚡ Fast startup: Embedded
php.inituned for mobile.
✅ App Store note: Interpreters are allowed if all code is bundled and no new executable code is downloaded at runtime (see Apple’s guidelines). This package is designed for that model.
Swift (App) ──calls──> PhpEngine (Swift)
│
└── libphp-ios.a (static)
└─ executes bundled .php scripts
The package exposes PhpEngine which bootstraps the embedded PHP CLI entrypoint with an isolated working directory and passes argv/env. STDERR/STDOUT are captured and returned to Swift.
- iOS / iPadOS 16.0+
- Xcode 15+
- Swift 5.9+
- PHP scripts compatible with PHP 8.3+
Xcode → Package Dependencies → Add Package
URL: https://github.com/makalin/php-ios.git
Or in Package.swift:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "YourApp",
platforms: [.iOS(.v16)],
dependencies: [
.package(url: "https://github.com/makalin/php-ios.git", from: "0.1.0")
],
targets: [
.target(
name: "YourApp",
dependencies: [.product(name: "PhpIOS", package: "php-ios")],
resources: [.process("PhpScripts")] // your bundled .php files
)
]
)Create PhpScripts/hello.php in your app target:
<?php
// PhpScripts/hello.php
$in = stream_get_contents(STDIN);
$payload = json_decode($in, true);
$name = $payload["name"] ?? "World";
echo json_encode(["greeting" => "Hello, $name!", "ts" => time()]);import PhpIOS
let engine = try PhpEngine.shared()
// Run inline code (equiv. to: php -r 'echo json_encode(["ok"=>true]);')
let inline = try engine.runInline("""
echo json_encode(["ok" => true, "php" => PHP_VERSION]);
""")
print(inline.json()?["php"] ?? "n/a")
// Run bundled script and pass JSON via STDIN
let request = ["name": "Mehmet"]
let result = try engine.runScript(
resource: .init(bundle: .main, path: "PhpScripts/hello.php"),
stdin: .json(request)
)
let greeting = try result.json()["greeting"] as? String
print(greeting ?? "-")let result = try engine.runScript(
resource: .init(bundle: .main, path: "PhpScripts/task.php"),
argv: ["--mode", "migrate"],
environment: ["APP_ENV": "production"]
)
print(result.stdout) // raw textpublic struct PhpResult {
public let exitCode: Int32
public let stdout: String
public let stderr: String
public func json() throws -> Any
}
public enum PhpInput {
case none
case text(String)
case data(Data)
case json(Any) // encodes to UTF-8 JSON
}
public final class PhpEngine {
public static func shared() throws -> PhpEngine
public func runInline(_ code: String,
stdin: PhpInput = .none,
ini: [String:String] = [:]) throws -> PhpResult
public struct ResourceRef { public let bundle: Bundle; public let path: String }
public func runScript(resource: ResourceRef,
argv: [String] = [],
stdin: PhpInput = .none,
env: [String:String] = [:],
ini: [String:String] = [:]) throws -> PhpResult
}- php.ini overrides: Pass
ini: ["memory_limit":"64M","display_errors":"0"]. - Working dir: Defaults to
Library/Caches/phpios. UsePhpEngine.configure(paths:)(optional) to relocate. - Extensions: Built-ins:
json,mbstring,pcre,ctype,filter,tokenizer,xml,dom,libzip. (See Building from source to customize.)
- Bundle all
.phpfiles as app resources. - Do not fetch or execute downloaded scripts or bytecode.
- Do not expose a general “code execution” UI for user-supplied scripts.
- Network use is fine for data, not for executable code.
- Templating: Render Markdown/HTML on device using a PHP library.
- Migrations: Run one-shot data transforms for local app storage.
- Domain logic: Reuse validated PHP algorithms/DSL parsers offline.
- Reports: Generate CSV/JSON from local data sets.
This repo includes:
/Toolchain/
build-php.sh # cross-compiles PHP 8.3 static for arm64-apple-ios
sdk/ # minimal headers/libs for iOS
patches/ # small portability tweaks (iconv, dlopen stubs)
/Sources/PhpIOS/
PhpBridge.mm # calls php_module_main()
PhpEngine.swift # public API
Resources/php.ini # mobile defaults
lib/libphp-ios.a # prebuilt (if not rebuilding)
To rebuild:
cd Toolchain
./build-php.sh --php=8.3.10 --extensions="json,mbstring,xml,dom,zip" --min-ios=16.0Outputs libphp-ios.a and headers placed under Sources/PhpIOS/lib/.
YourApp/
Package.swift
Sources/YourApp/
App.swift
ContentView.swift
PhpScripts/
hello.php
ContentView.swift:
import SwiftUI
import PhpIOS
struct ContentView: View {
@State private var output = "–"
var body: some View {
VStack(spacing: 16) {
Text("PHP-iOS Demo").font(.title2).bold()
ScrollView { Text(output).monospaced().frame(maxWidth: .infinity, alignment: .leading) }
Button("Run PHP") {
Task {
do {
let res = try PhpEngine.shared().runInline(#"echo "Hello from " . PHP_VERSION;"#)
output = res.stdout
} catch {
output = "Error: \(error)"
}
}
}
}.padding()
}
}- No JIT; Opcache JIT is disabled on iOS.
- FFI,
dl(), and dynamic loading are disabled. - Only bundled scripts may run (no code download).
- Long-running tasks should yield; consider chunked processing.
- Dyld errors: Ensure you’re linking the provided static lib; remove conflicting PHP libs from your project.
- Missing script: Verify resource path is included in target and
Package.swifthas.process("PhpScripts"). - Unicode issues: Use UTF-8 everywhere;
PhpInput.jsonhandles encoding.
- Optional SQLite, Intl, GMP builds
- Swift Concurrency helpers (
async/awaitwrappers) - SwiftUI sample app
- Composer-style autoloader for bundled libs
- Treat PHP input as untrusted.
- Avoid executing user-provided code.
- Validate/escape all data; prefer JSON IO.
- PHP core team
- Mobile cross-compile pioneers in the OSS community
MIT © 2025 Mehmet T. AKALIN