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