4: 数据

投票需要存储。Phlo 的 ORM 将表定义为类,对于一个教程应用,JSON 文件驱动程序是完美的:没有数据库服务器,没有凭据,每个表一个 JSON 文件。在本章中,您将定义模型,用选项填充它,并渲染真实记录。

4.1: 模型文件

创建 type.poll.phlo

@ extends: model

static table = 'poll'
static order = 'votes DESC'
static DB => %JSONDB(data.'poll.json')
static columns = 'id,option,votes'

static total => array_sum(array_map(fn($row) => (int)$row->votes, static::records()))

逐行解释:@ extends: model 使其成为一个模型类。文件名 type.poll.phlo 赋予类名称 type_poll(点变为下划线)。tablecolumns 描述了表:一个 id,选项文本(字符串)和投票计数(整数)。static DB => %JSONDB(data.'poll.json') 将模型指向 JSON 驱动程序;%JSONDB(...)phlo('JSONDB', ...) 的实例简写,而 data 是应用程序的数据路径常量,因此表位于 data/poll.json。计算的静态 total 汇总所有投票;你需要它来计算百分比。

需要记住的一个 JSONDB 特性:记录以普通数据对象的形式返回,因此将计算值(如 total)保留在类中作为静态或在控制器中,并通过静态模型方法而不是在记录实例上进行更改。

4.2: 打开数据库资源

资源是可选的。打开 data/app.json 并列出模型层所需的内容:

{
    "resources": [
        "DB/DB",
        "DB/model",
        "DB/JSONDB",
        "DB/JSON.result"
    ]
}

DB/model 是 ORM,DB/JSONDB 是文件驱动,DB/JSON.result 是其结果包装器,而 DB/DB 是共享基础。当你手动编辑 data/app.json 时,你需要自己列出要求;Phlo Control Center 会为你解决这些问题,文本编辑器则不会。

重建并进行 lint:

php www/app.php build::run
php www/app.php build::lint

build::run 打印新的编译文件(+type.poll.php+model.php+JSONDB.php,...),而 build::lint 返回 []

4.3: 播种选项

投票应该在第一次运行时创建自己的选项。在 poll.phlo 中,将 options prop 和 home method 替换为:

route GET poll => $this->home

method home {
    $this->seed
    view($this, 'Phlo Poll')
}

method seed {
    if (type_poll::records()) return
    foreach (['Phlo', 'Next.js', 'Laravel', 'Rails'] AS $option){
        type_poll::create(option: $option, votes: 0)
    }
}

type_poll::records() 获取所有记录;如果存在任何记录,则跳过种子填充。type_poll::create(...) 使用命名参数插入记录。注意 $this->seed 没有括号:没有参数的方法像属性一样被调用。

重新加载 http://localhost/poll 一次,然后查看存储:

docker run --rm -v $(pwd)/app:/app ghcr.io/q-ainl/phlo cat /app/data/poll.json
[
    {
        "option": "Phlo",
        "votes": 0,
        "id": 1
    },
    {
        "option": "Next.js",
        "votes": 0,
        "id": 2
    }
]

(加上 Laravel 和 Rails。)该表是一个可读的 JSON 文件;ids 是自动分配的。

4.4: 渲染记录

现在用记录替换静态按钮,并添加一个结果部分。完整的 poll.phlo 视图部分:

prop question = 'Which stack wins?'

method share($votes) => ($total = type_poll::total()) ? round($votes / $total * 100) : 0

view:
<main#app.poll>
    <h1>$this->question</h1>
    {{ $this->results }}
    {{ $this->choices }}
</main>

view results:
<section#results.card>
<foreach type_poll::records() AS $option>
    <div.option>
        <span.name>$option->option</span>
        <span.votes>$option->votes</span>
        <div.track>
            <div.bar style="width: {{ $this->share($option->votes) }}%"></div>
        </div>
    </div>
</foreach>
</section>

view choices:
<section.card>
<foreach type_poll::records() AS $option>
    <button>$option->option</button>
</foreach>
</section>

share 方法将投票计数转换为百分比;{{ ... }}style 属性内调用它。#results id 在后面很重要:它是你将要就地更新的元素。将条形样式添加到 poll.style.phlo 中,在 <style ns=app> 块内:

.option {
    display: grid
    grid-template-columns: 1fr auto
    gap: 4px 12px
    margin-bottom: 14px
}
.track {
    grid-column: 1 / -1
    height: 8px
    border-radius: 4px
    background: $border
}
.bar {
    height: 100%
    border-radius: 4px
    background: $primary
    transition: width .4s
}
.votes: color: $muted

重新加载 http://localhost/poll:四个选项,投票计数为 0,空条形。是时候让人们投票了。

我们使用必要的cookie来使该网站正常工作。在您的许可下,我们还使用分析工具来改善网站。