Download the PHP package yangweijie/perry-php without Composer
On this page you can find all versions of the php package yangweijie/perry-php. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download yangweijie/perry-php
More information about yangweijie/perry-php
Files in yangweijie/perry-php
Package perry-php
Short Description Cross-platform UI abstraction and build system inspired by Perry-TS
License MIT
Informations about the package perry-php
Perry PHP
Cross-platform UI abstraction and code generation framework. Define UI once in PHP, generate native code for Apple, Android, Windows, Linux, and Web platforms.
Table of Contents
- Installation
- Quick Start
- UI Components
- Text — Text Display
- Button — Clickable Button
- VStack — Vertical Layout
- HStack — Horizontal Layout
- Spacer — Flexible Space
- Image — Image Display
- ScrollView — Scrollable Container
- TextInput — Text Input Field
- Toggle — Toggle Switch
- AppContainer — Root App Container
- State Management
- Binding — Reactive Data Binding
- State / StateId — Low-level State
- Actions
- Simple Actions
- Closure Actions (AST-based)
- Supported PHP Features
- PHP Function Mappings
- Styling
- Style Builder
- Style Properties Reference
- Platform Support Matrix
- Code Generation
- Backends
- Generators
- IR System
- Platform Support
- Build System
- CLI Usage
- Examples
- Extending Perry
- Adding a Custom Widget
- Adding a Custom Backend
- Adding a Custom Generator
- Adding PHP Function Mappings
- Architecture
Installation
Requirements: PHP 8.2+
Quick Start
UI Components
All components extend Perry\UI\Widget. Each widget has:
- A constructor that accepts its specific parameters
- A
kind()method returning theWidgetKindenum - A
style()method inherited fromWidgetfor fluent styling - A unique
handle()(auto-generatedWidgetHandle)
Text
Displays static text or reactive bound data.
Constructor:
| Parameter | Type | Description |
|---|---|---|
$content |
string\|Binding |
Static string or a Binding for reactive display |
Methods:
| Method | Returns | Description |
|---|---|---|
content() |
string |
Text content (empty string if bound) |
getBinding() |
?Binding |
The bound Binding, or null for static text |
How bindings work: When a Text widget receives a Binding, AppContainer::bindings() auto-collects it. The backend generates @State (Swift), const state = {} (JS), or mutableStateOf (Kotlin) for it.
Generated code:
Full example — a live clock display:
Button
Clickable button with label and optional action.
Constructor:
| Parameter | Type | Default | Description |
|---|---|---|---|
$label |
string |
— | Button text |
$action |
Action\|\Closure\|null |
null |
Click handler |
Methods:
| Method | Returns | Description |
|---|---|---|
label() |
string |
Button label text |
getAction() |
?Action |
Action object |
Generated code (Swift):
Generated code (HTML):
VStack
Vertical layout — arranges children top to bottom.
Constructor:
| Parameter | Type | Description |
|---|---|---|
...$children |
Widget |
Child widgets (variadic) |
Spacing: Controlled via Style::padding() on the VStack — the padding value becomes spacing in SwiftUI:
Generated code:
HStack
Horizontal layout — arranges children left to right.
Constructor:
| Parameter | Type | Description |
|---|---|---|
...$children |
Widget |
Child widgets (variadic) |
Generated code:
Spacer
Flexible space that expands to fill available area. Use inside HStack or VStack to push elements apart.
Constructor: No parameters.
Generated code:
Image
Displays an image from a path or resource name.
Constructor:
| Parameter | Type | Description |
|---|---|---|
$source |
string |
Image path or asset name |
Methods:
| Method | Returns | Description |
|---|---|---|
source() |
string |
Image source path |
Generated code:
ScrollView
Scrollable container for content that exceeds the viewport.
Constructor:
| Parameter | Type | Description |
|---|---|---|
...$children |
Widget |
Child widgets inside scroll area |
Generated code:
TextInput
Text input field with placeholder.
Constructor:
| Parameter | Type | Default | Description |
|---|---|---|---|
$value |
StateId |
— | State variable bound to input |
$placeholder |
string |
'' |
Placeholder text |
Methods:
| Method | Returns | Description |
|---|---|---|
value() |
StateId |
Bound state ID |
placeholder() |
string |
Placeholder text |
Generated code:
Toggle
Toggle switch with label.
Constructor:
| Parameter | Type | Default | Description |
|---|---|---|---|
$isOn |
StateId |
— | State variable bound to toggle |
$label |
string |
'' |
Toggle label |
Methods:
| Method | Returns | Description |
|---|---|---|
isOn() |
StateId |
Bound state ID |
label() |
string |
Label text |
Generated code:
AppContainer
Root application container. Wraps your widget tree, sets window dimensions, and auto-collects all Binding objects.
Constructor:
| Parameter | Type | Default | Description |
|---|---|---|---|
$content |
Widget |
— | Root widget tree |
$windowWidth |
?int |
null |
Window width in pixels |
$windowHeight |
?int |
null |
Window height in pixels |
...$extraBindings |
Binding |
— | Additional state bindings |
Methods:
| Method | Returns | Description |
|---|---|---|
content() |
Widget |
Root widget |
windowWidth() |
?int |
Window width |
windowHeight() |
?int |
Window height |
bindings() |
Binding[] |
All collected bindings |
Binding collection logic: AppContainer walks the entire widget tree and collects every Binding from Text widgets. Bindings passed as ...$extraBindings are also included. This is how the backends know which @State / const state variables to declare.
State Management
Binding
Declarative, two-way data binding. The primary way to manage state.
Constructor:
| Parameter | Type | Description |
|---|---|---|
$name |
string |
Variable name in generated code |
$initialValue |
mixed |
Default value (string, int, float, bool) |
How it works:
- Pass a
Bindingto aTextwidget:new Text($display) AppContainerauto-collects it- Backends generate state declarations:
- Swift:
@State private var display = "0" - JavaScript:
const state = { display: "0" } - Kotlin:
var display = mutableStateOf("0") - Dart:
var display = ValueNotifier("0") - C#:
var display = "0";
- Swift:
- When a button action modifies
$display, the generated code assigns to the state variable - Re-render updates all bound
Textwidgets
Using $count in actions (closure):
State / StateId
Lower-level state management for TextInput and Toggle widgets.
When to use:
Binding— Most cases. Declarative, auto-collected byAppContainer.State/StateId—TextInputandTogglewidgets requireStateId.
Actions
Simple Actions
Pre-built action types for common operations:
Widget Actions
Interactive widgets support Action for event handling:
Supported event properties:
| Widget | Event Property | Description |
|---|---|---|
Button |
action (constructor) |
Fires on click/tap |
Slider |
onChange |
Fires when value changes |
TextInput |
onChange |
Fires when text changes |
Toggle |
onToggle |
Fires when checked state changes |
Action types work across all widgets:
| ActionType | Button | Slider | TextInput | Toggle |
|---|---|---|---|---|
SetValue |
✅ | ✅ | ✅ | ✅ |
Append |
✅ | — | — | — |
Clear |
✅ | — | — | — |
Custom |
✅ | ✅ | ✅ | ✅ |
Closure |
✅ | ✅ | ✅ | ✅ |
Closure Actions
The most powerful action type. Write PHP closures that get parsed into an AST and cross-compiled to any target language.
How it works:
Closure bindings (parameter substitution):
Nested closure pattern (for parameterized buttons):
Supported PHP Features
| Feature | Swift | JavaScript | Kotlin | Dart | C# |
|---|---|---|---|---|---|
| Variables | var x |
let x |
var x |
var x |
var x |
| State vars | x = ... |
state.x = ... |
x.value = ... |
x.value = ... |
x = ... |
| If/else | if {} else {} |
if {} else {} |
if {} else {} |
if {} else {} |
if {} else {} |
| While | while {} |
while {} |
while {} |
while {} |
while {} |
| For | for {} |
for {} |
for {} |
for {} |
for {} |
| Foreach | for x in y |
for x of y |
for x in y |
for x in y |
foreach x in y |
| Ternary | c ? a : b |
c ? a : b |
if (c) a else b |
c ? a : b |
c ? a : b |
| Switch | switch {} |
switch {} |
when {} |
switch {} |
switch {} |
| Match | match {} |
switch+return |
when-> |
switch+IIFE |
switch expr |
| Try/catch | do{}catch{} |
try{}catch{} |
try{}catch{} |
try{}catch{} |
try{}catch{} |
| Throw | throw |
throw |
throw |
throw |
throw |
| Type cast | Int(), Double() |
parseInt(), parseFloat() |
.toInt(), .toDouble() |
int.parse(), double.parse() |
(int), (double) |
| Increment | += 1 |
x++ |
x++ |
x++ |
x++ |
| Compound assign | +=, -=, *=, /= |
+=, -=, *=, /= |
+=, -=, *=, /= |
+=, -=, *=, /= |
+=, -=, *=, /= |
| Nullsafe | ?.method() |
?.method() |
?.method() |
?.method() |
?.method() |
| Static call | Class.method() |
Class.method() |
Class.method() |
Class.method() |
Class.method() |
PHP Function Mappings
| PHP | Swift | JavaScript | Kotlin | Dart | C# |
|---|---|---|---|---|---|
substr($s, -1) |
String(s.last!) |
s.slice(-1) |
s.last().toString() |
s[s.length-1] |
s[s.Length-1] |
substr($s, 0, -1) |
String(s.dropLast(1)) |
s.slice(0,-1) |
s.dropLast(1) |
s.substring(0,s.length-1) |
s.Substring(0,s.Length-1) |
substr($s, 1) |
String(s.dropFirst(1)) |
s.slice(1) |
s.dropFirst(1) |
s.substring(1) |
s.Substring(1) |
strlen($s) |
s.count |
s.length |
s.length |
s.length |
s.Length |
floatval($x) |
Double(x) ?? 0 |
parseFloat(x) |
x.toDoubleOrNull() ?: 0.0 |
double.parse(x.toString()) |
Convert.ToDouble(x) |
intval($x) |
Int(x) |
parseInt(x) |
x.toIntOrNull() ?: 0 |
int.parse(x.toString()) |
Convert.ToInt32(x) |
strval($x) |
String(x) |
String(x) |
x.toString() |
x.toString() |
x.ToString() |
in_array($x, $a) |
a.contains(x) |
a.includes(x) |
a.contains(x) |
a.contains(x) |
a.Contains(x) |
strpos($s, $n) |
s.firstIndex(of: n) |
IIFE with indexOf |
s.indexOf(n) |
s.indexOf(n) |
s.IndexOf(n) |
end($a) |
a.last! |
a[a.length-1] |
a.last() |
a.last |
a[a.Length-1] |
number_format($n,$d) |
String(format: "%.$df", $n) |
n.toFixed(d) |
String.format("%.$df",n) |
n.toStringAsFixed(d) |
$n.ToString("F$d") |
floor($x) |
floor(x) |
Math.floor(x) |
Math.floor(x).toInt() |
x.floor() |
Math.Floor(x) |
empty($x) |
x.isEmpty |
!x |
x.isEmpty() |
x.isEmpty |
string.IsNullOrEmpty(x) |
count($a) |
a.count |
a.length |
a.size |
a.length |
a.Length |
preg_split('/[...]/', $s) |
s.components(separatedBy:) |
s.split(/regex/) |
s.split().toRegex() |
s.split(RegExp(...)) |
Regex.Split(s,...) |
=== false optimization: All generators detect expr === false and emit !expr instead.
Styling
Style Builder
Fluent API — chain methods to compose styles:
Methods:
| Method | Parameters | Description |
|---|---|---|
make() |
— | Create new Style |
set(StyleProperty, $value) |
enum, mixed | Set any property |
get(StyleProperty) |
enum | Get property value |
has(StyleProperty) |
enum | Check if property is set |
all() |
— | Get all properties |
merge(Style) |
Style | Merge another style (right wins) |
backgroundColor(hex) |
string | Background color |
foregroundColor(hex) |
string | Text/icon color |
fontSize(float) |
float | Font size in points |
fontWeight(string) |
string | Font weight ('bold', 'light', etc.) |
textAlignment(string) |
string | Text alignment ('center', 'left', 'right') |
padding(float) |
float | Uniform padding |
paddingAll(top, bottom, leading, trailing) |
float×4 | Individual padding |
width(float) |
float | Fixed width |
height(float) |
float | Fixed height |
cornerRadius(float) |
float | Corner radius |
opacity(float) |
float | Opacity (0.0–1.0) |
border(width, color) |
float, string | Border width + color |
shadow(color, radius, offsetX, offsetY) |
string, float×3 | Drop shadow |
Style Properties Reference
| Property | Enum | Type | Description |
|---|---|---|---|
| Background Color | StyleProperty::BackgroundColor |
string |
Background color (hex) |
| Foreground Color | StyleProperty::ForegroundColor |
string |
Text/icon color (hex) |
| Border Color | StyleProperty::BorderColor |
string |
Border color (hex) |
| Border Width | StyleProperty::BorderWidth |
float |
Border width |
| Corner Radius | StyleProperty::CornerRadius |
float |
Corner rounding |
| Opacity | StyleProperty::Opacity |
float |
Transparency (0–1) |
| Padding | StyleProperty::Padding |
float |
Uniform padding |
| Padding Top | StyleProperty::PaddingTop |
float |
Top padding |
| Padding Bottom | StyleProperty::PaddingBottom |
float |
Bottom padding |
| Padding Leading | StyleProperty::PaddingLeading |
float |
Left padding |
| Padding Trailing | StyleProperty::PaddingTrailing |
float |
Right padding |
| Margin | StyleProperty::Margin |
float |
Uniform margin |
| Width | StyleProperty::Width |
float |
Fixed width |
| Height | StyleProperty::Height |
float |
Fixed height |
| Font Size | StyleProperty::FontSize |
float |
Font size |
| Font Weight | StyleProperty::FontWeight |
string |
Font weight |
| Font Family | StyleProperty::FontFamily |
string |
Font family |
| Text Alignment | StyleProperty::TextAlignment |
string |
Text alignment |
| Shadow Color | StyleProperty::ShadowColor |
string |
Shadow color |
| Shadow Radius | StyleProperty::ShadowRadius |
float |
Shadow blur |
| Shadow Offset X | StyleProperty::ShadowOffsetX |
float |
Shadow X offset |
| Shadow Offset Y | StyleProperty::ShadowOffsetY |
float |
Shadow Y offset |
| Text Decoration | StyleProperty::TextDecoration |
string |
Text decoration |
| Line Spacing | StyleProperty::LineSpacing |
float |
Line spacing |
| Min Width | StyleProperty::MinWidth |
float |
Minimum width |
| Min Height | StyleProperty::MinHeight |
float |
Minimum height |
| Max Width | StyleProperty::MaxWidth |
float |
Maximum width |
| Max Height | StyleProperty::MaxHeight |
float |
Maximum height |
Platform Support Matrix
All 6 backends now support the full set of 28 StyleProperties and event system (Button/Slider/TextInput/Toggle Actions):
| Feature | macOS (SwiftUI) | iOS (SwiftUI) | Android (XML) | Android (Compose) | Web (HTML) | Linux (Gtk4) | Windows (WinUI) |
|---|---|---|---|---|---|---|---|
| StyleProperties | |||||||
| BackgroundColor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| ForegroundColor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| BorderWidth/BorderColor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| CornerRadius | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Padding (all edges) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Margin (all edges) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Width / Height | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| FontSize / FontWeight / FontFamily | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| TextAlignment | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Shadow (color/radius/offset) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| TextDecoration | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| LineSpacing | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Min/Max Width/Height | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Opacity | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Event System | |||||||
| Button action (Click) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Slider onChange | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| TextInput onChange | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Toggle onToggle | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Widgets | |||||||
| Slider / TextInput / Toggle | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| NavigationView / TabView | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| List | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Support levels:
| Level | Description |
|---|---|
Wired |
Fully supported, generates native code |
Stub |
Stub implementation (tvOS, visionOS, watchOS) |
Missing |
Not yet implemented |
NotApplicable |
Not applicable for this platform |
Code Generation
Backends
| Backend | Class | Platforms | Output |
|---|---|---|---|
swiftui |
SwiftUIBackend |
macOS, iOS, tvOS, visionOS, watchOS | SwiftUI Swift |
html |
HtmlBackend |
Web, WebAssembly | HTML/CSS/JavaScript |
compose |
ComposeBackend |
Android | Jetpack Compose Kotlin |
android-xml |
AndroidXmlBackend |
Android | Android XML layouts |
winui |
WinUIBackend |
Windows | WPF/WinUI XAML |
gtk4 |
Gtk4Backend |
Linux | GTK4 XML UI |
Generators
Generators transform IR into target language code. Each implements Perry\IR\Generator.
| Generator | Language | State Var | New Var |
|---|---|---|---|
SwiftGenerator |
Swift | name = ... |
var name = ... |
JavaScriptGenerator |
JavaScript | state.name = ... |
let name = ... |
KotlinGenerator |
Kotlin | name.value = ... |
var name = ... |
DartGenerator |
Dart | name.value = ... |
var name = ... |
CSharpGenerator |
C# | name = ... |
var name = ... |
IR System
54 intermediate representation node types:
Core (14): Program, Assignment, IfStatement, BinaryOp, UnaryOp, Variable, Literal, FunctionCall, ReturnStatement, ArrayAccess, MethodCall, PropertyAccess, Ternary, ArrayLiteral
Loops (5): WhileStatement, ForStatement, ForeachStatement, BreakStatement, ContinueStatement
Switch/Match (3): SwitchStatement, CaseNode, MatchExpression
Output (2): EchoStatement, PrintStatement
Type System (1): Cast
Inc/Dec (2): Increment, Decrement
Compound Assignment (5): PlusAssign, MinusAssign, MulAssign, DivAssign, ModAssign
Binary Ops (11): PowOp, BitwiseAnd, BitwiseOr, BitwiseXor, ShiftLeft, ShiftRight, SpaceshipOp, CoalesceOp, LogicalAnd, LogicalOr, LogicalXor
Unary Ops (2): UnaryPlus, BitwiseNot
Nullsafe (2): NullsafeMethodCall, NullsafePropertyAccess
Exceptions (3): ThrowStatement, TryCatchStatement, CatchClause
Static (3): StaticCall, StaticPropertyAccess, ClassConstFetch
Include (1): IncludeStatement
Platform Support
| Platform | Target String | Backend |
|---|---|---|
| macOS | macos |
swiftui |
| iOS | ios |
swiftui |
| iOS Simulator | ios-simulator |
swiftui |
| tvOS | tvos |
swiftui |
| visionOS | visionos |
swiftui |
| watchOS | watchos |
swiftui |
| Android | android |
compose / android-xml |
| Linux | gtk4-linux |
gtk4 |
| Windows | windows |
winui |
| Web | web |
html |
| WebAssembly | wasm |
html |
Build System
Compile to native app:
Windows Requirements
Apps that use the WebView widget (e.g., the Pry JSON viewer) require WebView2 Runtime on Windows.
Install WebView2 Runtime:
-
Option 1 — Evergreen Bootstrapper (recommended, auto-updates):
Download from Microsoft Edge WebView2:
https://go.microsoft.com/fwlink/p/?LinkId=2124703 -
Option 2 — Evergreen Standalone Installer:
https://go.microsoft.com/fwlink/p/?LinkId=2124702 -
Option 3 — Fixed Version (for offline/restricted environments):
https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section -
Check if already installed:
OpenControl Panel → Programs and Featuresand look for WebView2 Runtime.
Or checkC:\Program Files (x86)\Microsoft\EdgeWebView\Application\.WebView2 ships with Microsoft Edge (Chromium-based), so it's often already present on modern Windows systems.
Build output: The compiler writes pry.html alongside the .exe file. The app reads it at runtime via WebView2's NavigateToString().
CLI Usage
Examples
Calculator
Full calculator with 7 actions, state management, and styling. Runs on macOS and web.
Simple Counter
Todo List
Pry — JSON Viewer (with WebView2 on Windows)
A native JSON viewer with tree view, search, syntax highlighting, and clipboard support. Uses WebView widget to embed a full HTML/JS UI.
Note for Windows: Requires WebView2 Runtime. The generated pry.html file contains the complete viewer UI (tree rendering, search, context menus) and is loaded at runtime.
Extending Perry
1. Adding a Custom Widget
Create a new widget class, add it to WidgetKind, and update all backends to handle it.
Step 1: Create the widget class
Step 2: Add enum case to WidgetKind
Step 3: Update each backend to generate code for Slider
Step 4: Repeat for each backend (KotlinGenerator, DartGenerator, CSharpGenerator, etc.)
2. Adding a Custom Backend
A backend converts the widget tree into target platform code.
Step 1: Create the backend class
Step 2: Register in CodegenFactory
Step 3: Use it
3. Adding a Custom Generator
A generator converts IR nodes into target language code.
Step 1: Create the generator class
Step 2: Use it with a backend
4. Adding PHP Function Mappings
Each generator maps PHP built-in functions to target language equivalents.
Example: Add array_map() support to SwiftGenerator
To add support across all generators:
- Add the mapping in
SwiftGenerator.php - Add the mapping in
JavaScriptGenerator.php - Add the mapping in
KotlinGenerator.php - Add the mapping in
DartGenerator.php - Add the mapping in
CSharpGenerator.php - Add tests in
tests/Generator/
Architecture
License
MIT