PurePHP 与 HTMX 集成
前置:组件;本页:用 HTMX 做无 JavaScript 的动态交互。
PurePHP 和 HTMX 构成强大的组合,让你能够构建动态、响应式的用户界面,同时保持 PHP 后端简洁。
HTMX 片段适合编译 Shape
HTMX 片段天然适合编译 Shape:hx-* 属性是普通的静态标签属性,因此在 Shape 上只设置一次,端点用请求数据渲染同一个 Shape。参见编译渲染。
为什么选择这个组合?
- PurePHP:提供组件化的 PHP 模板渲染
- HTMX:提供无 JavaScript 的动态交互能力
- 完美匹配:PurePHP 处理服务端渲染,HTMX 处理客户端交互
快速开始
1. 安装依赖
bash
composer require yonld/purephp然后在 HTML 中引入 HTMX:
html
<script src="https://unpkg.com/htmx.org@2.0.4"></script>2. 创建动态组件
计数器文本是绑定的 Slot;端点用新的计数渲染同一个 CounterValue() 组件。每个组件都是自己的单元:
php
<?php
use Pure\Component\Call;
use Pure\Core\Slot;
use function Pure\Component\{component, register};
use function Pure\HTML\p;
function CounterValue(mixed ...$children): Call
{
return component(__FUNCTION__, ...$children);
}
register(CounterValue(...),
factory: static fn () => p('Current count: ', Slot::value('count'))->id('counter'),
prepare: static function (int $count): array {
return ['count' => $count];
}
);php
<?php
require_once __DIR__ . '/CounterValue.cmp.php';
use Pure\Component\Call;
use Pure\Core\Slot;
use function Pure\Component\{component, register};
use function Pure\HTML\{button, div};
function Counter(mixed ...$children): Call
{
return component(__FUNCTION__, ...$children);
}
register(Counter(...),
factory: static fn () =>
div(
Slot::raw('counter'),
button('Increment')
->hx_post('/increment')
->hx_target('#counter')
->hx_swap('innerHTML')
)->class('counter'),
prepare: static function (int $count): array {
return ['counter' => CounterValue()->count($count)];
}
);php
<?php
require __DIR__ . '/components/Counter.cmp.php';
// 渲染页面
echo Counter()->count(0);
// 处理 HTMX 请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['REQUEST_URI'] === '/increment') {
$count = (int)($_COOKIE['count'] ?? 0) + 1;
setcookie('count', $count);
echo CounterValue()->count($count);
exit;
}3. 无限滚动列表
php
<?php
use function Pure\HTML\{button, div, li, ul};
function TodoList()
{
return div(
ul()->id('todos'),
button('Load More')
->hx_get('/todos?page=1')
->hx_target('#todos')
->hx_swap('beforeend')
)->class('todo-list');
}
// 处理 HTMX 请求
if ($_SERVER['REQUEST_METHOD'] === 'GET' && strpos($_SERVER['REQUEST_URI'], '/todos') === 0) {
$page = (int)($_GET['page'] ?? 1);
$todos = getTodos($page); // 获取待办事项列表
foreach ($todos as $todo) {
echo li($todo['title'])->class('todo-item');
}
exit;
}4. 实时搜索
结果列表是一个组件;每个结果标题都用 Slot::value() 绑定,端点用搜索结果渲染列表组件:
php
<?php
use Pure\Component\Call;
use Pure\Core\Slot;
use function Pure\Component\{component, register};
use function Pure\HTML\div;
function SearchResult(mixed ...$children): Call
{
return component(__FUNCTION__, ...$children);
}
register(SearchResult(...),
factory: static fn () => div(Slot::value('title'))->class('search-result'),
prepare: static function (string $title): array {
return ['title' => $title];
}
);php
<?php
require_once __DIR__ . '/SearchResult.cmp.php';
use Pure\Component\Call;
use Pure\Core\Slot;
use function Pure\Component\{component, register};
use function Pure\HTML\div;
function ResultList(mixed ...$children): Call
{
return component(__FUNCTION__, ...$children);
}
register(ResultList(...),
factory: static fn () => div(Slot::raw('results')),
prepare: static function (array $results): array {
$items = [];
foreach ($results as $result) {
$items[] = SearchResult()->title($result['title']);
}
return ['results' => $items];
}
);php
<?php
require_once __DIR__ . '/ResultList.cmp.php';
use Pure\Component\Call;
use Pure\Core\Slot;
use function Pure\Component\{component, register};
use function Pure\HTML\{div, input};
function SearchBox(mixed ...$children): Call
{
return component(__FUNCTION__, ...$children);
}
register(SearchBox(...),
factory: static fn () =>
div(
input()
->type('text')
->placeholder('Search...')
->hx_get('/search')
->hx_trigger('keyup changed delay:500ms')
->hx_target('#results'),
div(Slot::raw('list'))->id('results')
)->class('search-box'),
prepare: static function (iterable|string|Stringable $list): array {
return ['list' => $list];
}
);php
<?php
require __DIR__ . '/components/SearchBox.cmp.php';
// 用空结果列表渲染页面
echo SearchBox()->list(ResultList()->results([]));
// 处理 HTMX 请求
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_SERVER['REQUEST_URI'] === '/search') {
$query = $_GET['q'] ?? '';
$results = searchItems($query); // 搜索条目
echo ResultList()->results($results);
exit;
}