麵包屑導航是提升網站易用性、SEO 結構化資料的關鍵 UI 元件。本文從基礎路徑解析、動態標題映射、Schema.org 結構化資料輸出、快取優化到現代框架整合,提供完整的 PHP 實作方案。
<?php
function buildBreadcrumbs(array $labelMap = [], string $separator = ' > '): string
{
$requestUri = $_SERVER['REQUEST_URI'] ?? '/';
// 移除查詢字串
$path = parse_url($requestUri, PHP_URL_PATH) ?? '/';
$path = rtrim($path, '/');
$crumbs = [];
$currentPath = '';
// 根目錄
$crumbs[] = ['label' => '首頁', 'url' => '/', 'active' => false];
if ($path !== '') {
$segments = explode('/', ltrim($path, '/'));
foreach ($segments as $segment) {
$currentPath .= '/' . $segment;
$label = $labelMap[$segment] ?? ucfirst(str_replace(['-', '_'], ' ', $segment));
$crumbs[] = [
'label' => $label,
'url' => $currentPath,
'active' => false,
];
}
}
// 最後一項設為 active
if (!empty($crumbs)) {
$crumbs[count($crumbs) - 1]['active'] = true;
}
return renderBreadcrumbs($crumbs, $separator);
}
function renderBreadcrumbs(array $crumbs, string $separator): string
{
$html = '';
return $html;
}
// 使用範例
$labelMap = [
'service' => '有償服務',
'teach' => '家教中心',
'union' => '校園服務聯盟',
'job' => '學生兼職',
'free' => '資訊庫',
];
echo buildBreadcrumbs($labelMap);
// 輸出:首頁 > 有償服務 > 子分類 > 當前頁
?>
<?php
class BreadcrumbBuilder
{
private array $crumbs = [];
private array $routeMap = []; // 路由模式 => 標題生成器
public function addRoute(string $pattern, callable $labelGenerator): self
{
$this->routeMap[$pattern] = $labelGenerator;
return $this;
}
public function buildFromRequest(): self
{
$this->crumbs = [];
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?? '/';
$path = rtrim($path, '/');
$this->crumbs[] = ['label' => '首頁', 'url' => '/', 'active' => false];
if ($path !== '') {
$segments = explode('/', ltrim($path, '/'));
$currentPath = '';
foreach ($segments as $i => $segment) {
$currentPath .= '/' . $segment;
$isLast = ($i === count($segments) - 1);
// 嘗試匹配路由模式
$label = $segment;
$matched = false;
foreach ($this->routeMap as $pattern => $generator) {
// 簡單模式匹配:/user/{id}/post/{pid}
$regex = '@^' . preg_replace('/\{[^}]+\}/', '([^/]+)', $pattern) . '$@';
if (preg_match($regex, $currentPath, $matches)) {
array_shift($matches); // 移除完整匹配
$label = $generator(...$matches);
$matched = true;
break;
}
}
if (!$matched) {
$label = ucfirst(str_replace(['-', '_'], ' ', $segment));
}
$this->crumbs[] = [
'label' => $label,
'url' => $isLast ? null : $currentPath,
'active' => $isLast,
];
}
}
return $this;
}
public function render(string $template = ''): string
{
if ($template) {
// 自定義模板渲染
ob_start();
include $template;
return ob_get_clean();
}
return $this->defaultRender();
}
private function defaultRender(): string
{
$html = '';
return $html;
}
public function getJsonLd(): string
{
$items = [];
foreach ($this->crumbs as $i => $crumb) {
$items[] = [
'@type' => 'ListItem',
'position' => $i + 1,
'name' => $crumb['label'],
'item' => $crumb['url'] ? (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $crumb['url'] : null,
];
}
return json_encode([
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => $items,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}
// 使用範例
$builder = new BreadcrumbBuilder();
$builder->addRoute('/user/{id}', fn($id) => "用戶 #$id")
->addRoute('/article/{slug}', fn($slug) => ucfirst($slug))
->addRoute('/category/{name}/page/{page}', fn($name, $page) => "$name (第 $page 頁)");
$builder->buildFromRequest();
echo $builder->render();
// 頁面底部輸出 Schema.org JSON-LD
echo '';
?>
Google 搜尋結果會顯示麵包屑,提升點擊率:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "首頁", "item": "https://example.com/"},
{"@type": "ListItem", "position": 2, "name": "有償服務", "item": "https://example.com/service"},
{"@type": "ListItem", "position": 3, "name": "網站開發", "item": "https://example.com/service/web-dev"}
]
}
</script>
<?php
function getCachedBreadcrumbs(string $cacheKey, callable $generator, int $ttl = 3600): string
{
$cacheFile = sys_get_temp_dir() . '/breadcrumbs_' . md5($cacheKey) . '.cache';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $ttl) {
return file_get_contents($cacheFile);
}
$html = $generator();
file_put_contents($cacheFile, $html);
return $html;
}
// 使用
$cacheKey = $_SERVER['REQUEST_URI'];
$breadcrumbsHtml = getCachedBreadcrumbs($cacheKey, fn() => (new BreadcrumbBuilder())->buildFromRequest()->render());
echo $breadcrumbsHtml;
?>
<!-- resources/views/components/breadcrumb.blade.php -->
@props(['crumbs' => [], 'separator' => '>'])
<!-- 使用 -->
<?php
// src/Twig/BreadcrumbExtension.php
class BreadcrumbExtension extends \Twig\Extension\AbstractExtension
{
public function getFunctions(): array
{
return [
new \Twig\TwigFunction('breadcrumbs', [$this, 'renderBreadcrumbs']),
];
}
public function renderBreadcrumbs(array $crumbs, string $separator = '>'): string
{
// 渲染邏輯...
}
}
?>
{# templates/_breadcrumb.html.twig #}
<nav aria-label="breadcrumb"> 標識導航區塊<ol> 語意化有序列表aria-current="page" 標識當前頁aria-hidden="true" 隱藏螢幕閱讀器| 場景 | 解決方案 |
|---|---|
| 單頁應用(SPA) | 前端路由更新麵包屑,或 SSR 預渲染 |
| 多語系網站 | 標題映射表按語系區分,或使用翻譯函數 __() |
| 動態參數過多(如 /search/q/foo/page/2/sort/desc) | 只顯示主要層級,參數頁面合併為「搜尋結果」 |
| 深層巢狀超過 5 層 | 截斷中間層級,顯示「...」或僅保留前 2 + 後 2 |
| 非標準 URL(如 rewrite 後) | 使用路由解析器而非直接解析 REQUEST_URI |
優質的麵包屑導航需要:正確的路徑解析、靈活的標題映射、語意化 HTML、Schema.org 結構化資料、快取機制、無障礙支援。建議封裝為可複用的類別或元件,整合至專案的佈局模板中,並在路由/控制器層提供動態標題覆寫機制。