1: 介绍
Phlo 是一种编程语言和引擎,基于 PHP 8+,旨在构建紧凑、清晰和高性能的 web 应用程序。路由、控制器代码、视图、样式和前端更新形成一个整体。Phlo 将 .phlo 转换为普通的 PHP 类,并生成所需的资产。
Web 服务器指向你的 webroot /www。未知路径被重写为 /www/app.php,在这里 Phlo 读取你的配置,管理实例并处理路由。构建阶段在源文件更改时 自动 (JIT) 运行。
1.1: 哲学
- 简短的代码,高表达性 – 最小的语法,没有分号,紧凑的路由。
- 自由而不干涉 – Phlo 不强制框架结构。
- 高宽平衡 – 逻辑关联而不是在文件夹中的碎片化。
- 通过简单性实现性能 – 转译的 PHP 在没有运行时惩罚的情况下运行;前端使用轻量级的 NDJSON 更新。
- 一个连贯的系统 – 路由、渲染、资产和前端行为是相互关联的。
1.2: 什么是Phlo
Phlo 是一个 PHP 的超集,具有自动构建功能。一个 .phlo 文件可以包含:
- 控制器代码 在顶层(在实例化后执行)
- 路由(以空格分隔)
- 方法 和 属性(包括带有
=>的懒加载属性) - 视图(HTML 缩写,
<if>,<foreach>) - 样式(无分号的紧凑 CSS)
- 前端脚本(与视图捆绑在一起)
Phlo 是模块化的,可以用于:全栈、仅前端引擎、类编写/代码生成或作为资产管道。
1.3: 安装
1) 放置引擎
将文件夹 phlo/ 放入你的项目中(在 /www 之外)。
2) Webserver → webroot /www
将 webserver 设置为 文档根目录为 /www 并将未知路径重写为 app.php。
Nginx
server {
root /路径/到/项目/www;
location / {
try_files $uri $uri/ /app.php?$query_string;
}
}
Apache (.htaccess 在 /www)
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ app.php [QSA,L]
静态文件(图片,app.js,app.css)将直接提供;只有未知路径会转到 Phlo。
3) /www/app.php 的内容
使用 有效的入口点 如在你的代码库中:
<?php
require('/srv/phlo/phlo.php');
phlo_app_jsonfile($app = dirname(__DIR__).'/', "$app/data/app.json");
这将执行:
- 加载引擎 (
phlo/phlo.php) - 确定项目路径 (
$app) - 读取并验证
/data/app.json(必需) - 初始化实例,执行控制器代码并启动路由
4) 构建 (JIT) 和脚本
- 引擎将在修改后的下一个请求时 自动 执行构建(JIT)。
- 如果你想 手动 构建(例如,在 CI 中),请通过调用
phlo_build()使用 PHP 来完成。
1.4: 项目结构
一个 Phlo 项目有一个固定的结构:
/www/ ← webroot (公开)
app.php ← 中央入口点
app.js ← 自动生成的前端包
app.css ← 自动生成的 CSS
/data/app.json ← 必需的配置
/phlo/ ← Phlo 引擎
/php/ ← 转换后的 PHP (自动)
重要事项:
-
app.js和app.css是 完全自动 从你的.phlo源和app.json设置中生成的。 → 你 永远不会手动编写或修改这些文件。 → 在每个构建阶段,它们会被 覆盖。 -
这些文件的内容取决于
/data/app.json中的phloNS、defaultNS、phloJS和 CSS 构建选项。 -
/www/app.php启动引擎并引用你的配置。 -
/php包含转换后的类。这些文件也不需要手动修改;它们在每个构建阶段生成或刷新。
2: 配置
每个 Phlo 应用都有一个 必需 的配置文件:
/data/app.json
引擎从 app.php 中读取此文件,并据此确定资源、库、打包和资产输出。
2.1: 目标和位置
- 位置:
/data/app.json(相对于项目基础)。 - 通过 Phlo 引擎由
/www/app.php加载。 - 严格的 JSON (没有注释,没有尾随逗号)。
- 路径可以使用占位符
%app/(将被app.php中的应用路径替换)。
2.2: 最低配置
{
"id": "MijnApp",
"version": ".1",
"host": "localhost",
"dashboard": "phlo",
"debug": true,
"build": {
"libs": []
}
}
为什么这样?
build.libs是必需的(可以是一个空数组)。- 所有其他
build选项都是 可选的,根据需要添加(源、命名空间、CSS/JS构建、压缩等)。
我们稍后将单独处理没有(完整)构建的生产/发布。
2.3: `build.sources`
仅在您想要的源路径比应用路径 /www/app.php 更多时需要。
{
"build": {
"sources": [
"%app/",
"/srv/phloCMS/",
"/srv/phloCMS/fields/"
],
"libs": []
}
}
- 每个路径可以是绝对路径或以
%app/开头。 - 所有这些路径中的
.phlo文件都计入路由、转译和资产。
2.4: `构建.库`
声明你希望预先加载并在项目中可识别的库(来自 phlo/libs/)。
{
"build": {
"libs": [
"DB/DB",
"DB/MySQL",
"model",
"payload"
]
}
}
- 项目对应于
phlo/libs/中的文件(不带扩展名)。 - 按需自动加载仍然存在:如果你的代码使用了尚未加载的库,Phlo 会自动加载它。
build.libs因此是你的 显式基线/预加载(并可能对顺序/初始化很重要)。
2.5: 命名空间和资产
使用仅当您想要发送捆绑/资产范围时。
{
"build": {
"libs": [],
"defaultNS": "app",
"phloNS": ["app", "cms"],
"iconNS": ["cms"],
"icons": "/srv/icons"
}
}
defaultNS– 默认命名空间,如果没有明确的ns。phloNS– 确定哪些命名空间将出现在前端捆绑包中以及Phlo为哪些命名空间将资产写入/www/(因此比仅仅是app.js/app.css更广泛:还包括额外的捆绑包、图标资产/精灵等,具体取决于每个命名空间中存在的代码/资产)。iconNS+icons– 图标集的命名空间和来源。
2.6: 前端构建选项
{
"build": {
"libs": [],
"phloJS": false,
"buildCSS": true,
"minifyCSS": false,
"buildJS": true,
"minifyJS": false
}
}
| 键 | 类型 | 意义 |
|---|---|---|
phloJS |
布尔 | 打包Phlo前端引擎(高级用例)。 |
buildCSS |
布尔 | 处理来自.phlo的样式并写入CSS资产(按命名空间)。 |
minifyCSS |
布尔 | 压缩CSS。 |
buildJS |
布尔 | 打包前端脚本并写入JS资产(按命名空间)。 |
minifyJS |
布尔 | 压缩JS。 |
重要: Phlo写入/www/的所有内容(如app.js、app.css 和任何命名空间/额外资产)都是生成的,并在构建时被覆盖。不要手动编辑。
2.7: 其他字段
routes、extends等可以被模块使用。- 路径中的
%app/在初始化时会被当前应用路径替换。 - 保持
app.json严格有效。
3: 语法与结构
Phlo 是一个 PHP 的超集,具有紧凑的无分号语法。在一个 .phlo 文件中,你可以组合路由、属性、方法、视图、样式、脚本和控制器代码。构建器会转译为普通的 PHP/JS/CSS。
3.1: 文件结构
顶级元素:
function— 项目全局 函数route— 路由到方法static— 静态值或静态箭头方法- controllercode — 上述块外的顶级语句
prop— 静态或计算属性(可以有参数)method— 生成类上的方法view 名称:— 视图<script>…</script>— 脚本<style>…</style>— 样式
最小有效示例:
route GET home => $this->main
prop title = '欢迎'
method main => view($this->home)
view home:
<h1>$this->title</h1>3.2: 控制器代码
顶级语句在块外形成文件的 控制器。
控制器在实例存在后运行(不在 __construct 中)——这防止了循环引用。
示例(无害的初始化):
prop initialized = false
$this->initialized = true3.3: 声明
- 不要分号:行末结束语句。
- 多行参数:每个参数行以逗号结束。
=>周围的空格。
示例:
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: '概述',
main: view($this->home),
)
apply (
title: '完成',
main: '<p>准备好了</p>',
)3.4: 变量与作用域
- 变量如在 PHP 中:
$naam。 - 全局实例 通过
%Naam(例如%MySQL,%payload)— 通过实例管理器延迟加载。 - 范围: 函数/方法内的局部变量;实例/类上的属性/静态;
%…在项目中共享。
3.5: 常量
精确如引擎中定义:
| 常量 | 意义 / 值 | ||
|---|---|---|---|
phlo |
当前 Phlo 版本(字符串) | ||
cli |
如果没有 REQUEST_METHOD 则为 true(CLI) |
||
async |
如果 HTTP_X_REQUESTED_WITH 等于 'phlo' 则为 true |
||
method |
'CLI' 或请求的 HTTP 方法 |
||
jsonFlags |
JSON_PRETTY_PRINT |
JSON_UNESCAPED_UNICODE |
JSON_UNESCAPED_SLASHES |
br |
'<br>' |
||
bs |
'\\'(反斜杠) |
||
bt |
'`'(反引号) |
||
colon |
':' |
||
comma |
',' |
||
cr |
"\r" |
||
dash |
'-' |
||
dot |
'.' |
||
dq |
'"'(双引号) |
||
eq |
'=' |
||
lf |
"\n" |
||
nl |
cr.lf → 实际上是 "\r\n" |
||
perc |
'%' |
||
pipe |
' |
'`' | |
qm |
'?' |
||
semi |
';' |
||
slash |
'/' |
||
space |
' ' |
||
sq |
"'"(单引号) |
||
tab |
"\t" |
||
us |
'_' |
||
void |
''(空字符串) |
这些常量在
.phlo中随处可用。
3.6: 字符串与运算符
字符串:'single' 和 "double"。
运算符:符合 PHP。Phlo 添加了更紧凑的语法(箭头主体,命名参数);语义保持不变。
3.7: 功能
功能是 项目全局 并写入 /php/app.php。
单行:
function add($a, $b) => $a + $b
多行:
function sum($a, $b){
return $a + $b
}3.8: 方法
方法属于从 .phlo 文件生成的类。
method hello($who) => '嗨 '.$who
method classify($x) {
if ($x > 5) return '大'
return '小'
}
箭头用于单行逻辑;多行使用大括号。
3.9: 道具
静态(转译为 PHP 类属性;不允许函数调用):
prop title = '应用'
prop defaults = ['theme' => '黑暗']
计算属性(惰性 + 缓存):
prop now => time()
prop fullName => $this->first.' '.$this->last
带参数的属性(仅计算):
prop repeat($n) => str_repeat('*', $n)
# 使用
$this->repeat(5)3.10: 静力学
定义:
static x = 1
static y => time()
在同一类中调用:
dx (
static::$x,
static::x(),
static::y,
)
外部(通过类名)调用:
dx (
test::$x,
test::x(),
test::y,
)
重要:
- 没有参数的属性和方法可以不带
()调用。 - 静态方法在调用时始终需要
()。 - 也可以将静态值(如
x = 1)作为方法调用(x()),以保持一致的语法。
3.11: 命名参数
完全支持;使调用显式且减少错误。
%MySQL->delete (
'Users',
'id=?',
id: 1,
)3.12: 错误处理
- 基于 PHP 的错误/异常。
debug: true在app.json中激活通过 Phlo 引擎的详细调试。
3.13: 风格规则
- 空格在
=>周围 - 单行
if/foreach可以不使用大括号;多行 ⇒ 必须使用大括号 - 多行参数 ⇒ 行末加逗号
- 视图:
view 名称:+ 内容行,无额外缩进 - CSS 中不使用内联大括号
4: 路由
在 Phlo 中,路由将一个 以空格分隔的路径 + HTTP 方法 关联到一个 目标(通常是一个方法)。来自所有 .phlo 文件的路由被收集;路由器通过 app::route() 激活。
4.1: 基础形式
路由 [异步|两者] [GET|POST|PUT|DELETE|PATCH] 填充 [填充2 ...] => 目标
- 填充 需要用 空格 编写(在路由定义中不使用
/)。 - 目标:一个直接调用或语句块。
示例:
路由 GET 主页 => $this->main
方法 main => 视图($this->home)4.2: 同步 / 异步 / 两者皆可
| 关键词 | 行为 |
|---|---|
| (省略) | 仅 同步 (普通 HTTP) |
异步 |
仅 异步 (来自 Phlo-前端的请求) |
两者 |
同步 和 异步均可 |
route 两者 GET 数据 => $this->loadData
route 异步 POST 项目 保存 => $this->saveItems4.3: 变量
Phlo 解析每个路径段。以 $ 开头的段是 变量,具有额外的功能:
4.3.1 必需(传递给目标)
route GET user $id => $this->showUser($id)
method showUser($id) => view($this->profile)
4.3.2 可选存在 使用 ? → 布尔值
route GET search $full? => $this->search($full)
- 匹配
/search和/search/full。 $full在段full存在时为 true,否则为 false。 (实现会字面检查请求段是否等于 名称,不带?。)
4.3.3 剩余(可变长度)使用 =*
route GET file $path=* => $this->serveFile($path)
- 将所有剩余段匹配为
$path中的 一个 字符串。
4.3.4 默认值 使用 =
route GET page $slug=home => $this->page($slug)
- 没有段 →
$slug = 'home'。 - 有段 →
$slug = '<值>'。
4.3.5 长度要求 使用 .N
route GET code $pin.6 => $this->enter($pin)
- 仅当
$pin的长度恰好为 6 时匹配。
4.3.6 选择列表 使用 :a,b,c
route GET report $range:daily,weekly,monthly => $this->report($range)
- 段必须是 指定值 中的一个。
- 在缺失(空)且 有 默认值时,将应用默认值。
- 否则匹配失败。
你可以 组合 这些形式。示例:
# 枚举 + 必需 id
route GET export $fmt:csv,json $id => $this->export($fmt, $id)
# 枚举 + 默认
route GET theme $name:light,dark=light => $this->theme($name)4.4: 有效载荷检查与 `@`
您指定了带有一个 @ 的 确切 body-keys 和 逗号分隔 列表。路由器将其与 %payload 中的键进行 逐一比较(确切的集合;顺序与引擎提供的相同)。
route POST user @name,email => $this->createUser
method createUser => dx(%payload->name, %payload->email)
Body-keys 不 作为方法参数绑定;您通过
%payload读取它们。
4.5: 目标
本地方法
route GET profile show => $this->show
method show => view($this->profile)
外部类方法(静态)
route GET api version $major => api::getVersion($major)
- 静态调用:括号是必需的,即使没有参数。
- 明确传递路径变量。
4.6: 激活路由器
路由仅在之后匹配:
app::route()
例如,将此调用放在app.phlo(或其他中央控制器)中,在应用初始化之后和404处理的回退之前。
4.7: 推荐结构
- 将路由放在每个文件的顶部。
-
保持路径和方法名称逻辑一致:
route GET users list => $this->listUsers route POST users add => $this->addUser - 变量 始终 在目标中传递。
- 仅在端点故意需要同时支持同步和异步时使用
both。 - 使用选择列表
:…而不是单独的if分支来处理固定变体。
5: 视图
在 Phlo 中,你可以直接在 .phlo 文件中定义视图。视图是一个以 view 开头的命名或匿名块,包含 HTML(加上最小的 Phlo 结构)。
5.1: 声明
-
命名
view home: <h1>欢迎</h1> -
无名 → 名称自动为
viewview: <p>测试</p>
5.2: 论点
视图可以有 参数(包括默认值):
view($x = 0):
<p>值: $x</p>
view detail($input):
<p>输入: $input</p>
调用:
method show => view($this->detail('abc'))
method show2 => view($this->view(5))
与方法一样,一旦提供参数,括号是必需的。
5.3: 单行和多行
-
单行视图
view: <p>测试</p> -
多行视图: 块 在空行处结束
view: <p>行 1</p> <p>行 2</p> view nextView: <p>等等</p>
5.4: 速记HTML
紧凑的HTML简写会自动转换:
# shorthand
<p#id.class1.class2/>
# equivalent
<p id="id" class="class1 class2"></p>
- 标签上的尾部斜杠会导致闭合标签自动被放置。
5.5: 变量和表达式
5.5.1 直接变量和 单一属性
你可以 直接 在视图中写:
view($name):
<p>你好 $name,现在是 $this->time。</p>
直接允许:普通变量和 单一 属性访问(如
$this->time)。 不允许 在 HTML 中直接使用链式访问或函数/方法调用。
5.5.2 函数、方法、链式 或复杂表达式
使用 表达式标记:
view($x = 1):
<p>{{ $this->call('test') }}</p>
<p>{( $x > 1 ? '多个' : '一个' )}</p>
<p>{{ $this->members->all }}</p>
{{ … }}和{( … )}用于 评估 函数/方法/复杂或链式表达式。- 在文本 和 属性值中使用这些标记(见 5.6)。
(我们在这里故意不对这两种形式施加额外的语义;两者都用于内联表达式。)
5.6: 属性值
属性值 可以不加引号 如果它们 不包含 空格、@ 或变量:
view:
<p title=correct>正确答案</p>
<a href=/test1 data-value="$this->value">链接</a>
<a href=/test2 data-value="{{ $this->compute('value') }}">链接</a>
- 如果包含变量或表达式 ⇒ 使用引号(或在引号内使用表达式标记)。
5.7: 声明
使用 标签 在 HTML 中进行控制流:
view:
<p>列表:</p>
<foreach $this->list AS $key => $value>
<p>项目:$key</p>
<if $value > 1>
<p>高值:$value</p>
<elseif $value === 1>
<p>正好 1:$value</p>
<else>
<p>其他值:$value</p>
</if>
</foreach>
foreach以<foreach …>开始,以</foreach>结束。if/elseif/else一起以</if>结束。
5.8: 查看渲染
一个视图 通过 view($this->Naam)(或无名的 view) 作为调用 来 渲染。
这个调用 发送输出 并 结束脚本。 apply() 也是如此。
route GET home => $this->home
method home => view($this->home)
view home:
<h1>$this->title</h1>
不要这样做:
# ❌ 错误 – 视图“连接在一起”是不存在的;第一个调用结束。
method dashboard {
view($this->header)
view($this->content)
}
如果你想要多个输出片段:创建一个视图 来合并它们,或者在视图内部构建。
5.9: 最佳实践
- 保持视图 简洁 和清晰;将大部分拆分开。
- 在 HTML 中使用 直接变量/单一属性;所有更复杂的内容放在
{{ … }}或{( … )}中。 - 用 空行 结束多行视图;尽可能使用 单行。
- 使用简写以实现紧凑的标记,但保持可读性。
- 记住
view()和apply()是 终止 的:没有“粘贴渲染”。
6: CSS
Phlo 使用一种紧凑的、不带分号的 CSS 语法在 <style> 块中。
你写的规则使用 冒号 作为分隔符:
选择器: 属性: 值- 嵌套:
A: B: 属性: 值→ 输出:A B { 属性: 值; }
规则:
- 一条规则 = 一个声明。 源中没有分号;引擎会添加它们。
- 冒号
:分隔链级和属性与值。 - 反斜杠
\在嵌套中 将 下一个选择器部分 附加到父级(粘合)。下一个部分可以是伪类(:…)、属性选择器([…])等。 @media (…)可以在选择器块中;Phlo 将其提升 到正确的位置,同时保留当前选择器。
6.1: `<style>`块
<style>
html: height: 100dvh
body {
background: #947b6c
font-family: Sans-serif
p: line-height: 2em
}
</style>
输出(概念):
html { height: 100dvh; }
body { background: #947b6c; font-family: Sans-serif; }
body p { line-height: 2em; }6.2: 网络与群体
带有双点的链;用逗号分组 — 完整上下文 将应用于每个项目。
<style>
body: h1, p: \:first-letter: color: green
</style>
- 上下文:
body - 目标:
h1和p(带有粘合的:first-letter) - 反斜杠 在
:first-letter前面将该部分粘合到链中的前一个选择器。
输出:
body h1:first-letter,
body p:first-letter { color: green; }6.3: 媒体查询在选择器中
你可以在选择器块内编写 @media (…) ,Phlo 会将其移动到正确的位置并保留选择器上下文:
<style>
h1 {
color: white
@media (max-width: 768px): color: black
}
</style>
输出:
h1 { color: white; }
@media (max-width: 768px){
h1 { color: black; }
}6.4: 变量
Phlo 支持 CSS 变量 通过 $namen。
你可以在 :root 中定义变量,或者在任何其他级别 — 但 :root 通常用于全局主题。
<style>
:root {
$background: #0d0d0d
$surface: #1a1a1a
$text: #ffffff
$accent: #ff4a00
}
body {
background: $background
color: $text
}
button {
background: $accent
color: $text
}
</style>
输出
:root {
--background: #0d0d0d;
--surface: #1a1a1a;
--text: #ffffff;
--accent: #ff4a00;
}
body {
background: var(--background);
color: var(--text);
}
button {
background: var(--accent);
color: var(--text);
}
👉 Phlo 自动将 $variabelen 转换为 --custom-properties 并在调用时使用 var(--...)。
你可以在任何地方重用变量 — 也包括在媒体查询和嵌套选择器中。
6.5: 动态变量
Phlo的前端引擎包含库 DOM/CSS.var,通过它你可以在CSS中定义的 $变量 直接从JavaScript访问和修改,通过全局的 app.var 对象。
每个 $变量 在你的CSS中会自动在 app.var.<名称> 下可用。
示例
<style>
:root {
$background: #0d0d0d
$text: #ffffff
}
</style>
<script>
app.var.background = '#000000'
const textColor = app.var.text
</script>
app.var.background = '#000000'→ 实时修改DOM中的--background值,无需重建或重新加载。const textColor = app.var.text→ 读取当前值。
👉 这些修改在浏览器中 实时 生效,并直接影响所有使用该变量的元素。
你可以用它来:
- 主题切换(深色/浅色模式)
- 根据用户输入动态调整强调颜色
- 切换交互式UI而无需单独的CSS类
工作原理
- CSS引擎将
$background转换为--background。 - 前端引擎通过
document.documentElement.style读取/写入这些值。 app.var提供一个简单的代理对象,让你可以像操作普通JS属性一样工作。
6.6: 完整示例
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: 最佳实践
- 使用
$变量来定义颜色、间距和字体 — 这使得主题和深色/浅色模式变得简单。 - 在
:root中定义全局主题变量。 - 使用选择器链和分组来编写紧凑、可读的代码。
- 将
@media直接放在块中;Phlo 会将它们整齐地放在正确的位置。 - 在嵌套中使用
\将伪类或属性粘贴到父选择器上。 - 代码中不要使用分号;Phlo 会确保正确的 CSS 输出。
7: 对象关系映射
Phlo包含一个强大的内置ORM,可以将数据库表定义为类。
模型可以通过columns快速定义,或通过声明式schema进行详细定义。
记录被视为instances,支持属性、方法、视图、关系和多种数据库引擎。
7.1: 基本原则
一个 ORM 模型是一个 .phlo 文件,包含:
@ class:模型(和表)的名称@ extends: modelstatic table和columns或schema- 可选:关系(
parent,child,many) - 属性、方法和视图按 记录实例 工作
示例:
@ class: user
@ extends: model
view => $this->name
static table = 'users'
static columns = 'id,name,email,active,created'7.2: 定义模型
7.2.1 使用 columns 的表(快速且轻量)
使用 columns 来创建简单的表格:
@ 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 使用 schema 和 field(...)(丰富且声明式)
使用 schema 一次性定义字段、关系和用户界面:
@ 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在与 PhloCMS 结合使用时尤其强大,但也可以独立使用。
7.3: 增删改查
# 获取
$user = user::record(id: 1)
$list = shipment::records(order: 'created DESC')
# 创建
$shipment = shipment::create(destination: '巴黎', user: 1)
# 编辑 & 保存
$shipment->destination = '里昂'
$shipment->objSave
# 删除
shipment::delete('id=?', $shipment->id)
record(...)→ 单个记录(或 null)records(...)→ 记录数组(类实例)create(...)→ 插入 + 立即获取objSave→ 实例保存(根据 id 插入/更新)delete(...)→ 使用 SQL-where 静态删除
7.4: 关系导航
关系通过属性可用:
| 类型 | 声明 | 使用 |
|---|---|---|
| parent | type: parent |
$shipment->user |
| child | type: child |
$user->shipments |
| many | type: many |
$user->groups |
多对多
type: many 使用一个中间表:
groups: field (
type: 'many',
obj: 'group',
table: 'user_groups',
)
导航:
$user = user::record(id: 1)
foreach ($user->groups as $group)
echo $group->title
关系被批量加载以提高性能。没有跨数据库连接;每个类从其自己的引擎加载。
7.5: 实例动态
每个记录都是你模型类的真实实例。
你可以使用属性、方法和视图来添加虚拟字段、计算或表示:
@ 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>
使用:
$shipment = shipment::record(id: 'AB12')
echo $shipment->summary
echo $shipment # 使用视图作为字符串
属性和方法始终作用于记录实例,而不是静态的。
7.6: 过滤和查询
所有查询方法都接受命名参数和类似SQL的过滤器:
shipment::records(destination: '巴黎')
shipment::records(where: 'valid=1 AND weight>10')
shipment::pair(columns: 'id,destination')
支持:where,order,group,joins,缓存和模式感知列。
7.7: 缓存和性能
ORM使用内部缓冲区(objRecords,objLoaded)进行关系查找,并通过以下方式进行可选的APCu缓存:
static objCache = true # 1天
# 或
static objCache = 600 # 10分钟
记录和关系按批次加载。
在循环中使用records()进行批量选择,而不是使用record()。
7.8: 多个引擎
Phlo 支持通过 prop DB 多个后端。
默认是 %MySQL,但你可以为每个模型设置任何引擎。
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'
不同引擎上的表可以在关系中组合;每个类获取自己的数据。
/data/creds.ini
对于 MySQL 和 PostgreSQL 等引擎,你可以将凭据放在:
/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 会通过 %creds->... 自动加载这些。
7.9: 功能概述
| 功能 / 属性 | 类型 | 描述 |
|---|---|---|
record(...) |
静态 | 获取1条记录(或null) |
records(...) |
静态 | 获取多条记录(数组) |
create(...) |
静态 | 插入 + 获取 |
objSave |
实例 | 插入或更新 |
delete(where, …) |
静态 | 删除 |
pair, item, column |
静态 | 快速查询助手 |
objParents / schema: parent |
声明性 | 父关系 |
objChildren / schema: child |
声明性 | 子关系 |
schema: many |
声明性 | 多对多 |
objCache |
静态 | 可选的 APCu 缓存 |
prop DB |
静态 | 每个模型引擎 |
7.10: 最佳实践
- 使用
columns进行快速、简单的模型。 - 使用
schema进行丰富的定义和CMS集成。 - 定义一个
view以获取字符串表示。 - 使用 props 进行虚拟字段。
- 使用
objSave而不是save。 - 在批量加载时使用
records()。 - 使用
prop DB按引擎分隔模型。 - 在
/data/creds.ini中使用凭据。 - 保持模型声明式;逻辑在 props/methods 中。
8: 实例管理
Phlo使用自己的实例管理器来高效且可预测地初始化和重用对象。该系统决定何时执行控制器代码,如何存储实例以及如何防止循环引用。
8.1: 基础解释
当你定义一个 .phlo 文件时,它会在构建阶段转换为一个类。
通过 %naam 对对象的每次调用都通过 实例管理器 (phlo() 在 /phlo/phlo.php 中) 进行。
示例:
prop title = '欢迎'
route GET home => $this->main
method main => view($this->home)
view home:
<h1>$this->title</h1>
当路由 /home 被调用时:
- 实例管理器检查是否已经存在该类的实例。
- 如果没有,则 创建并保存 该实例。
- 创建后将执行 控制器代码 (见 §8.2)。
- 然后调用请求的方法。
8.2: 控制器代码
所有在 .phlo 文件中不属于 route、prop、static、method、function、view、<style> 或 <script> 的代码都是 控制器代码。
这段代码将在 实例化后 执行,一旦实例完全存在。
示例:
prop ready = false
%session->start() # 控制器代码(顶层)
$this->ready = true
- 这段代码在实例的 第一次创建 时运行一次。
- 与
__construct的区别在于,控制器代码执行时实例 完全存在 → 这样可以避免循环引用和不完整的对象。
8.3: `__handle()`的角色
Phlo 为每个类生成一个特殊的 __handle() 方法。
该方法由实例管理器在 通过 %naam 请求实例时 调用。
__handle():
- 如果尚未执行,则执行控制器代码;
- 确保 props 和 statics 正确可用;
- 确保为下次调用缓存实例。
您无需自己调用或覆盖 __handle() — 它是生成的类和实例管理器的一部分。
8.4: 懒惰初始化
因为控制器代码 在构造后 执行,实例可以 相互引用 而不会发生不必要的递归创建。
示例:
# 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)
}
%a和%b是懒惰创建的。- 两个文件中的控制器代码在它们的实例完全存在时运行。
- 你可以毫无问题地相互引用,因为实例在控制器代码运行之前就已经存在。
8.5: 最佳实践
- 使用控制器代码进行 初始设置,而不是请求依赖的逻辑。
- 将控制器代码 放在最上面 或直接在 props 下面以便于查看。
- 避免在
__construct中产生副作用;使用控制器代码而不是自定义构造函数。 - 让实例通过
%名称自我延迟初始化,而不是手动创建。 - 有意识地使用控制器代码来解决循环引用。
9: 工具和命令行接口
Phlo拥有一个非常轻量级的工具链。不需要外部编译器或CLI框架:一切都在/phlo/中的Phlo引擎本身上运行。在请求期间,Phlo会自动处理生成的PHP、JS和CSS文件的构建和更新。你可以手动启动这个过程以进行CI/CD或预发布构建,但在正常设置中并不需要。
9.1: 构建过程
Phlo 将 .phlo 文件转译为 PHP、JS 和 CSS。
这发生在:
- 请求期间的自动更改(JIT 构建)。
- 可选手动,通过调用
phlo_build()(例如,从脚本或 CI 中)。
入口点:
/www/app.php– 中央 PHP 入口点/www/app.js– 自动生成的前端包/www/app.css– 自动生成的 CSS 包
在每个请求中,Phlo 会检查:
- 源文件是否有更改,
- 生成的文件是否是最新的,
- 如有必要,执行增量构建。
9.2: 命令行接口
Phlo 不需要外部 CLI 框架;你可以直接使用 php。
对于特殊情况(预构建、CI、暂存),你可以手动构建:
php -r "require 'phlo/build.php'; phlo_build();"
或者从自己的 PHP 脚本中:
<?php
require 'phlo/build.php';
phlo_build();
这将经历与运行时相同的构建过程,但在 请求上下文之外。
所以没有
phloCLI 命令或包。你只需使用 PHP 本身。
9.3: 调试模式
调试模式在 data/app.json 中设置,方法如下:
{
"debug": true
}
在调试模式下:
- 错误和回溯将在浏览器中显示。
- 对
.phlo文件的更改会立即重建。 - 前端的调试覆盖层处于活动状态(如果已配置)。
9.4: 部署
为了生产:
- 在
data/app.json中设置"debug": false。 - 手动执行
phlo_build()以便所有文件都已预先转译。 - 将
/www/文件夹上线(包括app.php、app.js、app.css)。 - 确保
/php/文件夹中的生成后端代码一起迁移。
Phlo 不依赖于 npm、webpack 或其他工具链。 所有工具都内置并直接在 PHP 下运行。
9.5: 最佳实践
- 让构建过程尽可能自动运行;手动构建仅用于发布或CI。
- 不要手动修改
/www/app.js或/www/app.css— 这些文件会被覆盖。 - 使用
php -r "require ...; phlo_build();"进行CI中的预编译。 - 在生产环境中将
"debug": false以最小化性能开销。 - 确保在部署中保持
/phlo/文件夹的最新状态,因为引擎位于其中。
✅ 正确解释构建过程(JIT + 通过 phlo_build() 手动)
✅ 没有虚假的CLI命令
✅ debug模式如同在app.json中
✅ 部署如Phlo实际执行的那样
✅ 正确处理 /www/app.js 和 /www/app.css(绝不要手动修改)
10: 翻译
Phlo 内置了对 多语言和动态翻译 的支持。引擎包含功能和库,使您可以 在线翻译 文本,应用语言切换,并 异步加载翻译 而不打断用户体验。
10.1: 语言助手
Phlo 提供了最常用语言的简短助手函数,可以直接在视图和代码中使用:
nl()– 荷兰语en()– 英语de()– 德语fr()– 法语es()– 西班牙语
在视图中的示例:
view:
<p>{( nl('Hallo wereld') )}</p>
<p>{( en('Hello world') )}</p>
语言助手:
- 直接返回相应语言环境中的字符串,
- 主要用于多语言内容块和静态文本。
10.2: 其他使用
对于动态翻译,Phlo 提供了两个核心功能:translate() 和 translation()。
translate()
- 同步,立即返回当前语言的字符串翻译。
- 适用于系统中已翻译的文本。
<p>{( translate('welcome_message') )}</p>
translation()
- 异步,用于动态或尚未加载的翻译。
- 首先 显示源文本,然后 加载翻译并在 UI 中更新。
- 翻译以应用程序当前活动语言代码的形式返回。
<p>{( translation('dynamic_intro_text') )}</p>
- 这可以防止页面渲染时的延迟。
- 前端引擎在后台执行翻译请求,并在翻译可用时调整文本。
10.3: 动态语言选择
主动语言可以通过前端动态设置和更改。 当用户切换语言时,UI会自动重新翻译而无需重新加载。
典型流程:
- 应用以默认语言(例如
nl)启动。 - 用户选择另一种语言。
- 前端引擎切换语言代码。
- 通过
translation()加载的文本将在新语言可用时重新翻译。 - 通过
nl()或en()等助手加载的文本将保持其固定值。
10.4: 最佳实践
- 使用
translate()处理本地或静态管理的文本。 - 使用
translation()处理来自外部来源或异步加载的文本。 - 使用语言助手(
nl(),en(),…)处理静态内容块或固定视图文本。 - 保持翻译键的一致性和结构化(例如
page.home.intro而不是散乱的字符串)。 - 将语言切换集中在应用程序中,以便所有 UI 元素一致地变化。
- 将翻译功能与 Phlo 的前端引擎结合,以实现无刷新流畅的语言切换。
11: 高级
Phlo被设计为一个模块化系统。你可以单独使用引擎的部分,结合现有项目或扩展自己的功能,而无需修改核心。
11.1: 模块化使用
Phlo 可以作为 完整框架 使用,也可以作为 部分 使用。
例如:
- 仅使用 前端引擎,不使用后端。
- 仅使用 后端转译器,例如作为类生成器或资源打包器。
- 仅在现有的 PHP 项目中使用特定库 (
/phlo/libs)。 - 将 Phlo 用作 路由 + 视图引擎,但保留自己的数据库层。
示例:在静态网站中仅使用前端
<script src="/app.js" defer></script>
然后调用 Phlo 前端功能,如 app.apply() 和 app.state,而不使用 .phlo 后端。
或者:将 Phlo 集成到现有的 PHP 应用程序中,仅在 sources 中添加几个 .phlo 文件。
Phlo 会将它们转译到 /php/,然后你可以直接使用生成的类。
11.2: 集成
你可以通过以下方式将 Phlo 集成到现有代码库中:
- 将 Phlo 引擎放在子目录中,例如
/vendor/phlo/。 - 将
app.php添加为所有不存在路由的中央路由器。 - 在
app.json中补充build.sources字段,添加你现有代码的路径,以及 Phlo 目录。
示例 data/app.json:
{
"id": "LegacyApp",
"version": ".1",
"host": "localhost",
"dashboard": "phlo",
"debug": true,
"build": {
"libs": ["session", "json"],
"sources": [
"%app/",
"/legacy/phlo/"
]
}
}
这样你可以保留现有的 PHP 代码,同时添加 .phlo 文件。
Phlo 会对其进行转译并将其添加到你的项目中,而无需重构整个结构。
11.3: 最佳实践
- 保持自定义命令简洁且可重用。
- 逐步添加集成;例如,可以先从路由或视图开始。
- 将常用的独立模块放在
/phlo/libs中,而不是单独处理。 - 使用
sources来合并多个代码库而不重复。 - 在与 Phlo 的更新机制结合时,充分测试自定义应用命令。
12: 附件
附件包含现成的示例、项目模板和额外的展示,展示了Phlo在实践中的应用。
本部分旨在作为参考资料和灵感来源——而不是作为主要文档。
12.1: 代码示例
一个小而易读的.phlo片段集合,展示了常用模式:
基本路由与视图
prop title = '欢迎'
route GET home => $this->main
method main => view($this->home)
view home:
<h1>$this->title</h1>
带变量的路由
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>
异步翻译
view:
<p>{( translation('welcome_text') )}</p>12.2: 项目模板
一个最小的项目结构,包含所有必需的部分:
/www/
app.php ← 中央入口点
app.js ← 生成的前端包
app.css ← 生成的CSS
/data/
app.json ← 必需的配置
/phlo/ ← Phlo引擎
/php/ ← 转换后的后端代码(自动)
/app/ ← 你的Phlo源文件 (*.phlo)
这是推荐的基础结构。
额外的sources可以在data/app.json中定义,如果你想组合多个源路径。
12.3: CSS 展示
一个 Phlo 紧凑 CSS 语法的视觉示例:
输入
<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>
输出
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模型
典型的 ORM 类型定义示例在 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)
}
仅获取记录:
$user = %users->record(id: 1)
dx($user->name)12.5: 进一步的资源
- phloCMS — 完整的基于 Phlo 的 CMS
- phloWS — Web 服务层
- phloWA — WhatsApp 网关模块
- demo.zip — 包含例如 CSS、视图和路由的展示