21: 哲学
本章解释了为什么 Phlo 以这种方式构建:解析器、转译器、运行时和工具背后的深思熟虑的权衡。简而言之:Phlo 是一个集成平台,拥有自己的全栈语言,其强大之处在于所有层的垂直集成,从代码到 fleet,只有一种语言和一种思维模型。
每个权衡背后都有一个意图:让个人或小团队重新掌握他们所构建的东西的所有权和监督权。许多现代网页开发在悄然与此背道而驰。依赖树过于复杂以至于无法审计,框架更新速度快于你学习的速度,仪式和样板代码掩盖了代码实际的功能,以及使用计量的托管服务,其账单在你成功时恰好增加。每一个都在你和你的软件之间增加了一层,直到没有一个人再能掌握全局,而运行它的成本也不再是你可以预测的。Phlo 将这些视为需要远离的范式,而不是需要采纳的。代码不携带任何不必要的装饰,因此它在一次审阅中保持可读和可审查。输出是你拥有的纯 PHP。它在你控制的机器上运行,成本与机器的使用情况挂钩,而不是每个请求。可读性、所有权和可负担性不是附加的功能;它们是核心要点,下面的部分将说明语言如何实现这些目标。
21.1: 一个系统,四个层次
Phlo 不是你添加到堆栈中的库;它就是堆栈。四个层次共享一个设计:
- 语言:自定义的
.phlo语法,转译为可读的 PHP、CSS 和 JavaScript。 - 应用平台:后端(
obj、资源、函数)和前端(phlo.js,集成的 SPA 引擎,apply()协议)使用相同的协议,因此一个 route 可以在不需要你编写客户端粘合代码的情况下更新页面。 - 服务器平台:Phlo Daemon 和 Phlo Realtime 用于实时,Phlo WhatsApp 作为 WhatsApp 网关,邮件,以及 FrankenPHP 生产模式的工作者。
- 操作平台:Phlo Dashboard(一个单独的应用程序)管理你的 fleet、主机、域、数据库和通知。
每一层单独来看都不算特别。它们结合在一起意味着你永远不需要在生态系统之间进行转换:相同的约定、相同的错误页面、相同的 CLI,从单一的 view 到一整套服务器。像“一个紧凑的 Laravel 或 HTMX 替代品”的比较只触及到一层,错失了要点。
21.2: 一个基于行的解析器,没有抽象语法树
Phlo 按行读取源代码:语句在行结束时结束,节点头部打开一个块,空行关闭一个 view。没有构建抽象语法树的词法分析器。整个解析器只有几百行,可以在一次坐下来时阅读,并且因为第 N 行映射到已知的输出行,源映射和错误页面几乎是免费的。
行终止符模型是完整且小巧的:每行都以 ; 结束,以 ( [ { } , 或 . 结尾的行是隐式续行,尾随的 \ 明确地继续(见第 4 章)。
代价是严格性:没有多行引用字符串,空行结束一个 view,CSS 声明保持在一行内。Phlo 的答案是更好的诊断,而不是更宽容的解析器:违规会停止构建,并提供 .phlo 文件和行号。一个真正的语法会以解析器的可读性为代价放宽规则,而这不是 Phlo 所做的妥协。
21.3: 转译为可读的 PHP
.phlo 转换为普通的 PHP 类:每个文件一个类,包含命名源的头部,以及每个类的源映射,记录 PHP 行到 .phlo 行。您可以随时查看生成的 PHP 代码,以确切了解运行的内容;没有隐藏的运行时解释模板。当运行时出现问题时,错误会被翻译回您编写的 .phlo 行,在错误页面和 Phlo Control Center 中显示。
代价是一个构建步骤。在开发中,它通过请求重建(X.6)消失;在生产中,您只需构建一次。
21.4: `obj` 魔法基类
每个转译的类都扩展了 obj:通过 __get/__set 访问任意数据,绑定闭包,以及作为 _name() 方法编写的计算属性,这些方法在首次访问时被调用而不带括号并进行缓存。在 .phlo 中,prop now => time() 转译为一个缓存的 _now()。一种访问模型取代了 getter、惰性初始化和值对象;同一个对象充当视图模型、记录和配置包。
代价是:魔法访问的静态分析性不如显式属性,而模型对缓存的要求也需要严格的纪律。obj 中的静态结构缓存仅持有类形状,从不持有请求或用户范围的值,这正是它在 worker 模式下保持安全的原因。
21.5: `phlo()` 作为一个小型服务注册表
phlo('MySQL') 返回一个共享实例;在 .phlo 源代码中,%MySQL 转换为正是该调用。每个类可以实现 __handle() 来决定其自身的身份(单例、按参数的多例、总是新的),并选择在 worker 请求之间存活。您可以在没有容器框架、配置或注解的情况下进行依赖查找,并且 %name 简写使调用位置简短且易于搜索。
诚实的标签是服务位置,而不是注入:依赖关系在调用位置是隐式的。作为交换,没有任何连接,注册表本身只有十几行代码。
21.6: 在开发中根据请求重新构建
使用 build: true,Phlo 会检查任何源文件是否发生变化,并在处理请求之前进行重建,经过节流以确保检查在热循环中是廉价的。编辑-刷新循环感觉像是解释执行,而运行时保持转译状态,没有观察者进程,也没有手动构建。
成本:在请求期间构建会写入文件,这在长时间运行的 worker 中是不安全的,因此 build 和 thread 是互斥的。开发使用按请求构建;生产在发布构建上运行 worker 模式。这个分离是有意为之,而不是需要工程师绕过的限制。
21.7: 零依赖
引擎自带 CSS 转换器、JS 压缩器、图标精灵构建器和 SPA 运行时,以及 150 多个捆绑资源,而不是一个包树。支持 Composer,但懒惰且可选。引擎没有其他需要审计的内容,按照自己的计划进行升级,并保持足够小,以便于记忆。对于一个以可读性为前提的平台,供应商树将削弱这一前提。
成本:Phlo 重新实现了成熟库已经解决的事情,因此这些实现必须经过测试,并且可能会有大型库不会遇到的边缘情况。赌注在于,这里一个小而拥有的表面比广度更有价值。
零依赖也是十五年辩论中的一个立场。JavaScript 时代将后端移到了越来越大的引擎和框架上,依赖树深到需要自己的审计工具,解决了服务器渲染堆栈从未遇到的问题:构建链、补水、状态同步。Phlo 刻意走向相反的方向,回到一直默默支撑网络的技术:PHP 仍然运行着超过 70% 的已知服务器端语言的网站(W3Techs)。Phlo 将其创新投入到语言和开发者体验上,而不是重建从未是问题的基础设施。
在这场辩论中,有一个声音值得明确赞扬:Rich Harris。Svelte 证明了一个框架可以是一个编译器:声明式源代码最好在构建时转化为简单、直接的代码,因此框架本身在到达生产之前大部分消失。这个想法直接启发了 Phlo。Svelte 将编译器带入组件,而 Phlo 将其扩展到整个堆栈:.phlo 编译为可读的 PHP、CSS 和 JavaScript,运行的输出是你可以打开和阅读的内容,而不是一个解释你意图的运行时。尽管结论不同,这种欣赏是真诚的:Svelte 押注于 JavaScript 生态系统,Phlo 则反对依赖于一个。
21.7.1 显式资源,无自动加载
对显式性的偏好同样适用于资源。应用在 data/app.json 中声明它所使用的内容;未声明的资源根本不存在,引用它会在早期失败,而不是被自动加载器召唤出来。这是故意的。资源不仅仅是一个类:它可以提供全局函数、前端资产和配置,而 PHP 根本无法自动加载函数,因此懒加载器只能半加载一个,导致失败推迟并远离其原因。魔法加载还会使清单失去意义:你将无法再阅读 data/app.json 并了解一个应用的构成,构建也无法再编译出一个封闭、完整的包。
因此,清单保持为唯一的真实来源,缺失的资源是一个明确、即时的错误,指明缺失的内容。添加一个资源也不是一项繁琐的工作:在开发中,Phlo Control Center(在 build 和 debug 开启时位于 /phlo)列出了完整的目录,显示资源之间的要求,并为你编辑 data/app.json 中的资源列表。
21.8: 自托管所有权
一个 Phlo 应用是您控制的服务器上的文件目录。它作为一个 FrankenPHP 进程运行,在工作模式下保持在内存中,并在没有每次调用计量器运行的情况下响应请求。它的成本是服务器,而不是使用账单:同一台机器为您的第百位访客和第十万位访客提供服务,发票上的数字不会随着成功而上升。
这与云和无服务器默认设置之间的距离是故意的,在这种情况下,当您没有用户时账单最小,而一旦有用户,每个请求的费用就会增加。对于一个有效的产品,这种模式可能会将增长变成负担:使用越多,运行的成本就越高,有时超出数字仍然合理的点。Phlo 则反其道而行。您拥有语言输出(可读的 PHP)、数据(您管理的文件或数据库)以及所有运行的机器。Phlo Dashboard 将其作为您的 fleet 管理,而不是您从未见过的租用容量。
自托管并不意味着不可扩展。在负载均衡器后面,一个无状态的 PHP 应用层,数据库或缓存中共享状态的架构,已经在网络上运行了超过 25 年的最大网站。Phlo 直接融入其中:它转译为普通的 PHP 在 FrankenPHP 上,工作模式是每个请求的无共享,因此您可以通过在负载均衡器后面运行更多相同的节点来扩展应用层,以可预测的每节点成本。多个节点需要共享会话状态(将 PHP 的会话存储指向 Redis 或数据库,或使用粘性会话),数据库成为承担真正扩展工作的层,复制和分区,正如任何这样的堆栈。应用层是便宜且简单的部分;数据层是工程的所在。部署文档涵盖具体的多节点设置。
成本:拥有也意味着责任。您提供服务器,您进行补丁,您进行备份,您携带呼叫器。没有自动扩展到零,也没有管理控制平面来吸收操作负担。赌注是,对于大多数产品,一个您理解的可预测服务器胜过一个您无法理解的弹性账单。
21.9: 以代理为先的设计
SKILL.md 是一个完整的语言和构建参考,旨在使 AI 代理能够在没有先前知识的情况下工作;reflect:: CLI 公开了作为 JSON 的路由、视图、解析结构、搜索和依赖图;应用程序保留一个 data/app.md 草稿,代理首先读取并在之后更新。使 Phlo 对你可读的相同属性(一个闭环、源映射错误、一个单一的技能文档)使其对代理可行,并将其视为一项首要目标可以使小团队的效能倍增。
成本:SKILL.md 和 reflect:: 是合同的一部分。对语言、构建或 CLI 的更改在文档反映之前不会完成。这个维护负担是故意接受的。
21.10: Phlo 不是什么
诚实地面对边界:
- 一个年轻的生态系统。 没有插件市场,没有 Stack Overflow 归档。捆绑的资源和指南就是生态系统。
- 构造上有主见。 行解析器的严格性、视图中的空行规则、文件名中的点而非下划线:这些都是不可配置的。
- 不是依赖注入。
%instance是服务定位;如果你想要显式的构造函数连接,Phlo 会感觉是隐式的。 - Realtime 是一个处理程序代理,而不是流媒体引擎。 Phlo Realtime 通过 Phlo Daemon 将每个事件分发到你的 PHP 钩子,默认情况下是一次性处理(每个事件一个进程,简单而稳健),或者在需要吞吐量时通过常驻工作池处理。它通过增加工作者和节点来扩展,但它仍然是一个每事件模型,而不是每秒每个套接字处理数千条消息的低延迟总线。
- 开发和生产是不同的模式。 按请求重建和工作模式在设计上是互斥的;你不能同时拥有两者。
如果这些约束适合你,你将获得本章所描述的回报:一种语言和一种思维模型,从你的第一个视图到你的整个 fleet 垂直集成。