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: Switch on the database resources

Resources are opt-in. Open data/app.json and list what the model layer needs:

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

DB/model is the ORM, DB/JSONDB the file driver, DB/JSON.result its result wrapper, and DB/DB the shared base. When you edit data/app.json by hand you list requirements yourself; the Phlo Control Center resolves them for you, the text editor does not.

The next request rebuilds all of this automatically; the CLI shows exactly what transpiles:

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

build::run prints the new transpiled files (+type.poll.php, +model.php, +JSONDB.php, ...), and build::lint returns [].

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: Render the records

Now replace the static buttons with records, and add a results section. The full poll.phlo view section:

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>

The share method turns a vote count into a percentage; {{ ... }} calls it inside the style attribute. The #results id matters later: it is the element you will update in place. Add the bar styles to poll.style.phlo, inside the <style> block:

.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

Reload http://localhost/poll: four options, vote counts at 0, empty bars. Time to let people vote.

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