EN | NL | 中文

1: Introduction

Phlo is a programming language and engine built on top of PHP 8+, designed to create compact, clear, and performant web apps. Routing, controller code, views, styling, and frontend updates form a cohesive whole. Phlo transpiles .phlo to regular PHP classes and generates the necessary assets.

The web server points to your webroot /www. Unknown paths are rewritten to /www/app.php, where Phlo reads your configuration, manages instances, and handles routes. The build phase runs automatically (JIT) as soon as source files change.

1.1: Philosophy

1.2: What is Phlo

Phlo is a superset of PHP with automatic build. A .phlo file can contain:

Phlo is modularly deployable: full-stack, frontend-engine-only, class-writer/codegen, or as an asset pipeline.

1.3: Installation

1) Place Engine

Place the folder phlo/ in your project (outside /www).

2) Webserver → webroot /www

Set the webserver with document root to /www and rewrite unknown paths to app.php.

Nginx

server {
    root /path/to/project/www;

    location / {
        try_files $uri $uri/ /app.php?$query_string;
    }
}

Apache (.htaccess in /www)

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ app.php [QSA,L]

Static files (images, app.js, app.css) are served directly; only unknown paths go to Phlo.

3) Content of /www/app.php

Use the working entry point as in your codebase:

<?php
require('/srv/phlo/phlo.php');
phlo_app_jsonfile($app = dirname(__DIR__).'/', "$app/data/app.json");

What this does:

4) Build (JIT) and scripts

1.4: Project structure

A Phlo project has a fixed structure:

/www/              ← webroot (public)
  app.php          ← central entry point
  app.js           ← automatically generated frontend bundle
  app.css          ← automatically generated CSS

/data/app.json     ← required configuration

/phlo/             ← Phlo engine
/php/              ← transpiled PHP (automatically)

Important points:

2: Configuration

Each Phlo app has a mandatory configuration file:

/data/app.json

The engine reads this in from app.php and determines sources, libraries, bundling, and asset output.

2.1: Goal and position

2.2: Minimal configuration

{
  "id": "MyApp",
  "version": ".1",
  "host": "localhost",
  "dashboard": "phlo",
  "debug": true,
  "build": {
    "libs": []
  }
}

Why like this?

We will handle production/release without (full) build separately later.

2.3: `build.sources`

Only needed if you want more source paths than the app path from /www/app.php.

{
  "build": {
    "sources": [
      "%app/",
      "/srv/phloCMS/",
      "/srv/phloCMS/fields/"
    ],
    "libs": []
  }
}

2.4: `build.libs`

Declare which libraries (from phlo/libs/) you want to preload and have known by your project.

{
  "build": {
    "libs": [
      "DB/DB",
      "DB/MySQL",
      "model",
      "payload"
    ]
  }
}

2.5: Namespaces & assets

Use only if you want to send bundling/asset-scopes.

{
  "build": {
    "libs": [],
    "defaultNS": "app",
    "phloNS": ["app", "cms"],
    "iconNS": ["cms"],
    "icons": "/srv/icons"
  }
}

2.6: Frontend build options

{
  "build": {
    "libs": [],
    "phloJS": false,
    "buildCSS": true,
    "minifyCSS": false,
    "buildJS": true,
    "minifyJS": false
  }
}
key type meaning
phloJS bool Bundle Phlo frontend engine (advanced use cases).
buildCSS bool Process styles from .phlo and write CSS assets (per namespace).
minifyCSS bool Minify CSS.
buildJS bool Bundle frontend scripts and write JS assets (per namespace).
minifyJS bool Minify JS.

Important: Everything that Phlo writes to /www/ (such as app.js, app.css and any namespaced/extra assets) is generated and will be overwritten during builds. Do not edit manually.

2.7: Other fields

3: Syntax & Structure

Phlo is a superset of PHP with compact, semicolon-free syntax. In a single .phlo file, you combine routes, props, methods, views, styles, scripts, and controller code. The builder transpiles to regular PHP/JS/CSS.

3.1: File structure

Top-level elements:

Minimal, valid example:

route GET home => $this->main

prop title = 'Welcome'

method main => view($this->home)

view home:
<h1>$this->title</h1>

3.2: Controller code

Top-level statements outside of blocks form the controller of the file. The controller runs after the instance exists (not in __construct) — this prevents circular references.

Example (harmless init):

prop initialized = false
$this->initialized = true

3.3: Statements

Examples:

if ($active) $count = $count + 1

foreach ($rows AS $r) $sum = $sum + $r->value

foreach ($rows AS $r){
  $sum = $sum + $r->value
  $n = $n + 1
}

chunk (
  title: 'Overview',
  main: view($this->home),
)

apply (
  title: 'Ready',
  main: '<p>Done</p>',
)

3.4: Variables & Scopes

3.5: Constants

Exact as defined in the engine:

Constant Meaning / Value
phlo Current Phlo version (string)
cli true if there is no REQUEST_METHOD (CLI)
async true if HTTP_X_REQUESTED_WITH equals 'phlo'
method 'CLI' or the HTTP method of the request
jsonFlags JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
br '<br>'
bs '\\' (backslash)
bt '`' (backtick)
colon ':'
comma ','
cr "\r"
dash '-'
dot '.'
dq '"' (double quote)
eq '='
lf "\n"
nl cr.lf → effectively "\r\n"
perc '%'
pipe ' | '
qm '?'
semi ';'
slash '/'
space ' '
sq "'" (single quote)
tab "\t"
us '_'
void '' (empty string)

These constants are available everywhere in .phlo.

3.6: Strings & Operators

Strings: 'single' and "double". Operators: according to PHP. Phlo adds more compact syntax (arrow-bodies, named arguments); semantics remain the same.

3.7: Functions

Functions are project-global and are written to /php/app.php.

Singleline:

function add($a, $b) => $a + $b

Multiline:

function sum($a, $b){
  return $a + $b
}

3.8: Methods

Methods belong to the class generated from the .phlo file.

method hello($who) => 'Hi '.$who

method classify($x) {
  if ($x > 5) return 'large'
  return 'small'
}

Arrow for single-line logic; multi-line uses braces.

3.9: Props

Static (transpiles to PHP class property; no function calls allowed):

prop title = 'App'
prop defaults = ['theme' => 'dark']

Computed (lazy + cached):

prop now => time()
prop fullName => $this->first.' '.$this->last

Props with arguments (only computed):

prop repeat($n) => str_repeat('*', $n)

# usage
$this->repeat(5)

3.10: Statics

Definition:

static x = 1
static y => time()

Calling within the same class:

dx (
  static::$x,
  static::x(),
  static::y,
)

Calling externally (via class name):

dx (
  test::$x,
  test::x(),
  test::y,
)

Important:

3.11: Named Arguments

Fully supported; makes calls explicit and less error-prone.

%MySQL->delete (
  'Users',
  'id=?',
  id: 1,
)

3.12: Error handling

3.13: Style guidelines

4: Routing

Routing in Phlo associates a space-separated path + HTTP method with a target (usually a method). Routes from all .phlo files are collected; the router is activated with app::route().

4.1: Base form

route [async|both] [GET|POST|PUT|DELETE|PATCH] pad [pad2 ...] => target

Example:

route GET home => $this->main
method main => view($this->home)

4.2: sync / async / both

Keyword Behavior
(omit) Only sync (regular HTTP)
async Only async (requests from Phlo-frontend)
both Sync and async allowed
route both GET data => $this->loadData
route async POST items save => $this->saveItems

4.3: Variables

Phlo parses each path segment. Segments that start with $ are variables with extra capabilities:

4.3.1 Required (passed to target)

route GET user $id => $this->showUser($id)
method showUser($id) => view($this->profile)

4.3.2 Optional presence with ?boolean

route GET search $full? => $this->search($full)

4.3.3 Rest (variable length) with =*

route GET file $path=* => $this->serveFile($path)

4.3.4 Default value with =

route GET page $slug=home => $this->page($slug)

4.3.5 Length requirement with .N

route GET code $pin.6 => $this->enter($pin)

4.3.6 Choice lists with :a,b,c

route GET report $range:daily,weekly,monthly => $this->report($range)

You can combine these forms. Examples:

# enum + required id
route GET export $fmt:csv,json $id => $this->export($fmt, $id)

# enum + default
route GET theme $name:light,dark=light => $this->theme($name)

4.4: Payload check with `@`

You specify exact body keys with one @ and a comma-separated list. The router compares this 1-to-1 with the keys from %payload (exact set; order as provided by the engine).

route POST user @name,email => $this->createUser

method createUser => dx(%payload->name, %payload->email)

Body keys are not bound as method parameters; you read them via %payload.

4.5: Targets

Local method

route GET profile show => $this->show
method show => view($this->profile)

External class method (static)

route GET api version $major => api::getVersion($major)

4.6: Activate router

Routes are only matched after:

app::route()

Place this call, for example, in app.phlo (or another central controller) after your app initialization and before a fallback for 404 handling.

4.7: Recommended structure

5: Views

In Phlo, you define views directly in .phlo files. A view is a named or unnamed block that starts with view and contains HTML (plus minimal Phlo constructs).

5.1: Declaration

5.2: Arguments

Views can have arguments (including defaults):

view($x = 0):
<p>Value: $x</p>

view detail($input):
<p>Input: $input</p>

Calling:

method show => view($this->detail('abc'))
method show2 => view($this->view(5))

Just like with methods, parentheses are required when you provide arguments.

5.3: Single line and multiline

5.4: Shorthand HTML

Compact HTML shorthand is automatically converted:

# shorthand
<p#id.class1.class2/>

# equivalent
<p id="id" class="class1 class2"></p>

5.5: Variables and expressions

5.5.1 Direct variables and single properties

You can write directly in the view:

view($name):
<p>Hello $name, it is now $this->time.</p>

Direct allowed: regular variables and single property access (like $this->time). No chained access or function/method calls directly in the HTML.

5.5.2 Functions, methods, chained or complex expressions

Use expression markers:

view($x = 1):
<p>{{ $this->call('test') }}</p>
<p>{( $x > 1 ? 'Multiple' : 'Single' )}</p>
<p>{{ $this->members->all }}</p>

(We intentionally do not impose any extra semantics on these two forms; both are intended for inline expressions.)

5.6: Attribute values

Attribute values may be without quotes if they do not contain spaces, @ or variables:

view:
<p title=correct>Correct answer</p>
<a href=/test1 data-value="$this->value">Link</a>
<a href=/test2 data-value="{{ $this->compute('value') }}">Link</a>

5.7: Statements

Use control-flow via tags in the HTML:

view:
<p>List:</p>
<foreach $this->list AS $key => $value>
  <p>Item: $key</p>
  <if $value > 1>
    <p>High value: $value</p>
  <elseif $value === 1>
    <p>Exactly 1: $value</p>
  <else>
    <p>Other value: $value</p>
  </if>
</foreach>

5.8: Render view

A view is rendered with view($this->Name) (or the anonymous view) as a call. This call sends the output and terminates the script. The same applies to apply().

route GET home => $this->home

method home => view($this->home)

view home:
<h1>$this->title</h1>

Do not do:

# ❌ error – "gluing" views together does not exist; the first call terminates.
method dashboard {
  view($this->header)
  view($this->content)
}

If you want multiple pieces of output: create one view that combines them, or build it up in the view itself.

5.9: Best practices

6: CSS

Phlo uses a compact, semicolon-free CSS syntax within <style> blocks.
You write rules with colons as separators:

Rules:

6.1: `<style>` block

<style>
html { height: 100dvh; }
body {
  background: #947b6c;
  font-family: Sans-serif;
}
body p { line-height: 2em; }
</style>

6.2: Chains & groups

Chain with double colons; group with comma — the full context is applied per item.

<style>
body: h1, p: \:first-letter: color: green
</style>

Output:

body h1:first-letter,
body p:first-letter { color: green; }

6.3: Media queries in selector

You can write @media (…) inside the selector block; Phlo moves it to the right place and retains the selector context:

<style>
h1 {
  color: white
  @media (max-width: 768px): color: black
}
</style>

Output:

h1 { color: white; }
@media (max-width: 768px){
  h1 { color: black; }
}

6.4: Variables

Phlo supports CSS variables via $names. You can define variables in :root, or at any other level — but :root is common for global theming.

<style>
:root {
  $background: #0d0d0d
  $surface: #1a1a1a
  $text: #ffffff
  $accent: #ff4a00
}

body {
  background: $background
  color: $text
}

button {
  background: $accent
  color: $text
}
</style>

Output

:root {
  --background: #0d0d0d;
  --surface: #1a1a1a;
  --text: #ffffff;
  --accent: #ff4a00;
}

body {
  background: var(--background);
  color: var(--text);
}

button {
  background: var(--accent);
  color: var(--text);
}

👉 Phlo automatically converts $variables to --custom-properties and uses var(--...) when calling. You can reuse variables anywhere — even within media queries and nested selectors.

6.5: Dynamic variables

Phlo's frontend engine contains the library DOM/CSS.var, which allows you to access and modify defined $variables in CSS directly from JavaScript, via the global app.var object.

Each $variable in your CSS is automatically available under app.var.<name>.

Example

<style>
:root {
  $background: #0d0d0d
  $text: #ffffff
}
</style>

<script>
app.var.background = '#000000'
const textColor = app.var.text
</script>

👉 These adjustments work real-time in the browser and directly affect all elements that use the variable.

You can use this for, among other things:

Functionality

6.6: Complete example

body {
  background: #947b6c;
  font-family: Sans-serif;
}
body h1:first-letter,
body p:first-letter {
  color: green;
}
body p {
  line-height: 2em;
}
h1 {
  color: white;
}
html {
  height: 100dvh;
}
p {
  color: navy;
}
p:last-child {
  color: yellow;
}
@media (max-width: 768px){
  h1 {
    color: black;
  }
}

6.7: Best practices

7: ORM

Phlo includes a powerful built-in ORM that allows you to define database tables as classes.
Models can be quickly defined via columns or extensively via a declarative schema.
Records are treated as instances, with support for props, methods, views, relationships, and multiple database engines.

7.1: Basic principles

An ORM model is a .phlo file with:

Example:

@ class: user
@ extends: model

view => $this->name

static table = 'users'
static columns = 'id,name,email,active,created'

7.2: Defining models

7.2.1 Plate with columns (fast and light)

Use columns for simple tables:

@ class: shipment
@ extends: model

view: $this->destination ($this->user)

static table = 'shipments'
static order = 'changed DESC'
static columns = 'id,user,destination,costs,valid,weight,shipped,created,changed'
static objParents = ['user' => 'user']
@ class: user
@ extends: model

view => $this->name

static table = 'users'
static order = 'changed DESC'
static columns = 'id,name,email,level,active,created,changed'
static objChildren = ['shipments' => 'shipment']

7.2.2 With schema and field(...) (rich and declarative)

With schema you define fields, relationships, and UI all at once:

@ class: shipment
@ extends: model

view: $this->destination ($this->user)

static table = 'shipments'
static schema => arr (
    id: field (type: 'token', length: 4, title: 'ID'),
    destination: field (type: 'text', required: true, search: true),
    user: field (type: 'parent', obj: 'user', required: true),
    costs: field (type: 'price', prefix: '€ '),
    valid: field (type: 'bool'),
    attachments: field (type: 'child', obj: 'attachment', list: true),
)
@ class: user
@ extends: model

view => $this->name

static table = 'users'
static schema => arr (
    id: field (type: 'token'),
    name: field (type: 'text', search: true, required: true),
    email: field (type: 'email', required: true),
    shipments: field (type: 'child', obj: 'shipment'),
    groups: field (type: 'many', obj: 'group', table: 'user_groups'),
)

schema is especially powerful in combination with PhloCMS, but also works standalone.

7.3: CRUD

# Retrieve
$user = user::record(id: 1)
$list = shipment::records(order: 'created DESC')

# Create
$shipment = shipment::create(destination: 'Paris', user: 1)

# Edit & save
$shipment->destination = 'Lyon'
$shipment->objSave

# Delete
shipment::delete('id=?', $shipment->id)

7.4: Relational navigation

Relationships are available via properties:

Type Declaration Usage
parent type: parent $shipment->user
child type: child $user->shipments
many type: many $user->groups

Many-to-many

type: many uses a pivot table:

groups: field (
    type: 'many',
    obj: 'group',
    table: 'user_groups',
)

Navigation:

$user = user::record(id: 1)
foreach ($user->groups as $group)
  echo $group->title

Relationships are batch-loaded for performance. No cross-DB joins occur; each class loads from its own engine.

7.5: Instance dynamics

Each record is a real instance of your model class.
You can use props, methods, and views to add virtual fields, calculations, or representations:

@ class: shipment
@ extends: model

prop summary => $this->destination.' ('.$this->user.')'
method tax => $this->costs * 0.21

view:
<p>$this->summary</p>
<p>{( $this->tax )}</p>

Usage:

$shipment = shipment::record(id: 'AB12')
echo $shipment->summary
echo $shipment   # uses view as string

Props and methods always operate on the record instance, not statically.

7.6: Filtering en queries

All query methods accept named arguments and SQL-like filters:

shipment::records(destination: 'Paris')
shipment::records(where: 'valid=1 AND weight>10')
shipment::pair(columns: 'id,destination')

Supported: where, order, group, joins, caching, and schema-aware columns.

7.7: Caching and performance

The ORM uses internal buffers (objRecords, objLoaded) for relational lookups and optional APCu caching via:

static objCache = true       # 1 day
# or
static objCache = 600        # 10 minutes

Records and relationships are loaded in batches. Use records() for bulk selections instead of record() in loops.

7.8: Multiple engines

Phlo supports multiple backends via prop DB. By default, it is %MySQL, but you can set any engine per model.

SQLite

prop DB => %SQLite(data.'users.db')
@ class: notes
@ extends: model

prop DB => %SQLite(data.'notes.db')
static table = 'notes'
static columns = 'id,title,body'

PostgreSQL

prop DB => %PostgreSQL
@ class: invoices
@ extends: model

prop DB => %PostgreSQL
static table = 'invoices'
static columns = 'id,customer_id,total,created'

Tables on different engines can be combined in relationships; each class retrieves its own data.


/data/creds.ini

For engines like MySQL and PostgreSQL, place your credentials in:

/data/creds.ini
[mysql]
host     = localhost
database = db_name
user     = db_user
password = db_password

[postgresql]
host     = localhost
database = my_pg_db
user     = pg_user
password = pg_pass

Phlo automatically loads these via %creds->....

7.9: Overview of functions

Function / Property Type Description
record(...) static Retrieves 1 record (or null)
records(...) static Retrieves multiple records (array)
create(...) static Insert + retrieve
objSave instance Insert or update
delete(where, …) static Deletion
pair, item, column static Quick query helpers
objParents / schema: parent declarative Parent relationships
objChildren / schema: child declarative Child relationships
schema: many declarative Many-to-many
objCache static Optional APCu caching
prop DB static Per-model engine

7.10: Best practices

8: Instance Management

Phlo uses its own instance manager to efficiently and predictably initialize and reuse objects. This system determines when controller code is executed, how instances are stored, and how circular references are prevented.

8.1: Basic explanation

When you define a .phlo file, it is converted into a class during the build phase.
Each call to an object via %name goes through the instance manager (phlo() in /phlo/phlo.php).

Example:

prop title = 'Welcome'

route GET home => $this->main

method main => view($this->home)

view home:
<h1>$this->title</h1>

When the route /home is called:

  1. The instance manager checks if an instance of this class already exists.
  2. If not, it is created and stored.
  3. After creation, the controller code is executed (see §8.2).
  4. Then the requested method is called.

8.2: Controller code

All code in a .phlo file that does not fall under route, prop, static, method, function, view, <style> or <script> is controller code.
This code is executed after instantiation, as soon as the instance is fully created.

Example:

prop ready = false

%session->start()   # controller code (top-level)
$this->ready = true

8.3: The role of `__handle()`

Phlo generates a special __handle() method for each class. This is called by the instance manager when an instance is requested via %name.

__handle():

You do not need to call or override __handle() yourself — it is part of the generated class and the instance manager.

8.4: Lazy initialization

Because controller code is executed only after construction, instances can refer to each other without unwanted recursive creation occurring.

Example:

# file a.phlo
prop message = 'A ready'

# file b.phlo
prop message = 'B ready'

# file main.phlo
route GET test => $this->show

method show {
  dx(%a->message, %b->message)
}

8.5: Best practices

9: Tooling & CLI

Phlo has a very lightweight toolchain. There is no external compiler or CLI framework needed: everything runs on the Phlo engine itself in /phlo/. During requests, Phlo automatically takes care of the building and updating of the generated PHP, JS, and CSS files. You can manually start this process for CI/CD or pre-release builds, but in normal setups, that is not necessary.

9.1: Build process

Phlo transpiles .phlo files to PHP, JS, and CSS. This happens:

The entry points:

For each request, Phlo checks:

  1. if there are changes in the source files,
  2. if the generated files are up-to-date,
  3. and performs an incremental build if necessary.

9.2: CLI

Phlo does not need an external CLI framework; you can just use php.
For special situations (prebuilds, CI, staging) you can build manually by:

php -r "require 'phlo/build.php'; phlo_build();"

or from your own PHP script:

<?php
require 'phlo/build.php';
phlo_build();

This goes through the same build process as at runtime, but outside the request context.

So there is no phlo CLI command or package. You just use PHP itself.

9.3: Debug mode

Debug mode is set in data/app.json via:

{
  "debug": true
}

In debug mode:

9.4: Deployment

For production:

  1. Set "debug": false in data/app.json.
  2. Run a manual phlo_build() so that all files are pre-transpiled.
  3. Put the /www/ folder online (including app.php, app.js, app.css).
  4. Ensure that the /php/ folder with generated backend code is moved as well.

Phlo has no dependencies on npm, webpack, or other toolchains. All tooling is built-in and runs directly under PHP.

9.5: Best practices


✅ Correct explanation of the build process (JIT + manual via phlo_build()) ✅ No fake CLI commands ✅ Debug mode as in app.json ✅ Deployment as Phlo actually does ✅ Correct handling of /www/app.js and /www/app.css (never change manually)

10: Translations

Phlo has built-in support for multilingualism and dynamic translation. The engine includes features and libraries that allow you to translate text inline, apply language switches, and load async translations without interrupting the user experience.

10.1: Language helpers

Phlo offers short helper functions for the most commonly used languages, which you can directly use in views and code:

Example in a view:

view:
<p>{( nl('Hallo wereld') )}</p>
<p>{( en('Hello world') )}</p>

The language helpers:

10.2: Other use

For dynamic translations, Phlo offers two core functions: translate() and translation().

translate()

<p>{( translate('welcome_message') )}</p>

translation()

<p>{( translation('dynamic_intro_text') )}</p>

10.3: Dynamic language selection

The active language can be dynamically set and changed via the frontend. When the user switches languages, the UI is automatically retranslated without a reload.

Typical process:

  1. App starts with a default language (e.g. nl).
  2. The user selects a different language.
  3. The frontend engine switches the language code.
  4. Texts loaded via translation() are retranslated as soon as the new language is available.
  5. Texts via helpers like nl() or en() continue to show their fixed value.

10.4: Best practices

11: Advanced

Phlo is designed as a modular system. You can use parts of the engine separately, combine them with existing projects, or expand with your own functionality, without having to modify the core.

11.1: Modular use

Phlo can be used both as a complete framework and in parts.
For example:

Example: use only the frontend in a static site

<script src="/app.js" defer></script>

and then call Phlo-frontend functions like app.apply() and app.state without a .phlo backend.

Or: integrate Phlo into an existing PHP application and add just a few .phlo files in sources.
Phlo will then transpile them to /php/, after which you can use the generated classes directly.

11.2: Integration

You can integrate Phlo into an existing codebase by:

  1. Placing the Phlo engine in a subdirectory, e.g. /vendor/phlo/.
  2. Adding app.php as a central router for all non-existing routes.
  3. Updating the build.sources field in app.json with the paths of your existing code, alongside the Phlo folders.

Example data/app.json:

{
  "id": "LegacyApp",
  "version": ".1",
  "host": "localhost",
  "dashboard": "phlo",
  "debug": true,
  "build": {
    "libs": ["session", "json"],
    "sources": [
      "%app/",
      "/legacy/phlo/"
    ]
  }
}

This allows you to keep existing PHP code while still adding .phlo files. Phlo transpiles them and adds them to your project without needing to overhaul your entire structure.

11.3: Best practices

12: Attachments

The attachments contain ready-made examples, project templates, and extra showcases that demonstrate how Phlo is applied in practice. This section is intended as a reference and source of inspiration — not as primary documentation.

12.1: Code examples

A collection of small, well-readable .phlo fragments that illustrate commonly used patterns:

Basic route with view

prop title = 'Welcome'

route GET home => $this->main

method main => view($this->home)

view home:
<h1>$this->title</h1>

Route with variable

route GET user $id => $this->show($id)

method show($id) {
  $user = %users->record(id: $id)
  view($this->profile, $user)
}

view profile($user):
<h1>$user->name</h1>

Async translation

view:
<p>{( translation('welcome_text') )}</p>

12.2: Project template

A minimal project structure with all required components:

/www/
  app.php       ← central entry point
  app.js        ← generated frontend bundle
  app.css       ← generated CSS
/data/
  app.json      ← required configuration
/phlo/          ← Phlo engine
/php/           ← transpiled backend code (automatically)
/app/           ← your Phlo source files (*.phlo)

This is the recommended basic structure. Extra sources can be defined in data/app.json if you want to combine multiple source paths.

12.3: CSS Showcase

A visual example of Phlo's compact CSS syntax:

Input

<style>
html: height: 100dvh
body {
  background: #947b6c
  font-family: Sans-serif
  p: line-height: 2em
}
body: h1, p: \:first-letter: color: green
h1 {
  color: white
  @media (max-width: 768px): color: black
}
p {
  color: navy
  \:last-child: color: yellow
}
</style>

Output

body {
  background: #947b6c;
  font-family: Sans-serif;
}
body h1:first-letter,
body p:first-letter {
  color: green;
}
body p {
  line-height: 2em;
}
h1 {
  color: white;
}
html {
  height: 100dvh;
}
p {
  color: navy;
}
p:last-child {
  color: yellow;
}
@media (max-width: 768px){
  h1 {
    color: black;
  }
}

12.4: ORM Models

Example of a typical ORM type definition in Phlo:

type users {
  static columns = null

  field(id, type: number, auto: true)
  field(name, type: text)
  field(email, type: text)
}

route GET users => $this->overview

method overview {
  foreach (%users->records(order: 'name') as $u)
    dx($u->id, $u->name)
}

Retrieve a single record:

$user = %users->record(id: 1)
dx($user->name)

12.5: Further sources