首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 實現無限分類:陣列存儲 + 非遞歸算法 + 文件緩存完整方案

     編輯:PHP 綜合知識

前言:為什麼不遞歸、不資料庫?

傳統無限分類常用「遞歸查詢 + 資料庫遞歸 CTE」或「PHP 遞歸函數」。但在小型專案、無資料庫環境、極致性能要求下,將分類樹完整載入 PHP 陣列、用非遞歸迭代算法生成層級、靜態化存檔,能達到零資料庫查詢、零遞歸棧溢出風險、微秒級讀取。本文提供一個生產可用的 Cat 類完整實現。

核心數據結構:sortlevel 編碼法

每個節點擁有 sortlevel 字串,規則:

  • 根節點:001、002、003...(固定 3 位)
  • 子節點:父 sortlevel + 3 位序號,如 001001、001002
  • 層級深度 = strlen(sortlevel) / 3
  • 同級排序 = 字典序排序 sortlevel

優點:無需 parent_id、無需遞歸、單次排序即得完整樹序。

完整類實現(Cat.php)

<?php
/**
 * 無限分類管理類
 * 儲存:data.php 返回陣列 $class = [ 'sortlevel' => ['sortname'=>..., 'sortlevel'=>...], ... ]
 * 特點:非遞歸、陣列操作、文件緩存、支援無限層級
 */
class Cat
{
    public $data = [];  // 所有分類陣列,鍵為 sortlevel

    public function __construct()
    {
        $file = __DIR__ . '/data.php';
        if (is_file($file)) {
            include $file;           // $class 陣列
            $this->data = $class ?? [];
        }
    }

    /** 取得所有分類,按 sortlevel 升序(即樹的前序遍歷) */
    public function getAll(): array
    {
        $arr = $this->data;
        uksort($arr, 'strcmp');      // 字典序排序 = 樹的正確順序
        return $arr;
    }

    /** 根據父級 sortlevel 生成新子節點的 sortlevel */
    public function createSortLevel(string $fatherLevel = ''): string
    {
        if ($fatherLevel === '') {                     // 根節點
            $max = '000';
            foreach ($this->data as $v) {
                if (strlen($v['sortlevel']) === 3 && $v['sortlevel'] > $max) $max = $v['sortlevel'];
            }
            $next = (int)$max + 1;
            return str_pad((string)$next, 3, '0', STR_PAD_LEFT); // 001, 002...
        }

        // 子節點:找同父級下最大的 sortlevel
        $prefix = $fatherLevel;
        $max = $prefix . '000';
        foreach ($this->data as $v) {
            if (strpos($v['sortlevel'], $prefix) === 0
                && strlen($v['sortlevel']) === strlen($prefix) + 3
                && $v['sortlevel'] > $max) {
                $max = $v['sortlevel'];
            }
        }
        $next = (int)substr($max, -3) + 1;
        return $prefix . str_pad((string)$next, 3, '0', STR_PAD_LEFT);
    }

    /** 新增分類並持久化 */
    public function add(string $sortname, string $fatherLevel = ''): bool
    {
        $newLevel = $this->createSortLevel($fatherLevel);
        $key = $newLevel;                       // 用 sortlevel 作鍵,天然有序唯一
        $this->data[$key] = [
            'sortname'  => $sortname,
            'sortlevel' => $newLevel,
        ];
        return $this->save();
    }

    /** 將 $this->data 寫入 data.php(原子寫入) */
    private function save(): bool
    {
        $file = __DIR__ . '/data.php';
        $tmp  = $file . '.tmp';
        $code = "<?php\nreturn [\n";
        foreach ($this->data as $k => $v) {
            $code .= "    '$k' => [\n";
            $code .= "        'sortname'  => " . var_export($v['sortname'], true) . ",\n";
            $code .= "        'sortlevel' => " . var_export($v['sortlevel'], true) . ",\n";
            $code .= "    ],\n";
        }
        $code .= "];\n?>";
        if (file_put_contents($tmp, $code) === false) return false;
        return rename($tmp, $file);             // 原子替換,避免讀到半寫檔
    }

    /** 前端顯示用:生成帶縮進的選項陣列 */
    public function getOptions(): array
    {
        $opts = [];
        foreach ($this->getAll() as $v) {
            $depth = strlen($v['sortlevel']) / 3 - 1;
            $prefix = str_repeat('— ', $depth);
            $opts[$v['sortlevel']] = $prefix . $v['sortname'];
        }
        return $opts;
    }
}

數據文件示例(data.php)

<?php
return [
    '001' => ['sortname' => '電腦數碼', 'sortlevel' => '001'],
    '001001' => ['sortname' => '手機通訊', 'sortlevel' => '001001'],
    '001001001' => ['sortname' => '智能手機', 'sortlevel' => '001001001'],
    '001002' => ['sortname' => '電腦整機', 'sortlevel' => '001002'],
    '002' => ['sortname' => '家用電器', 'sortlevel' => '002'],
    '002001' => ['sortname' => '冰箱冷柜', 'sortlevel' => '002001'],
];

前端控制器示例(cat.php)

<?php
require_once 'Cat.php';
$cat = new Cat();

if (($_GET['action'] ?? '') === 'add') {
    $name = trim($_POST['sortname'] ?? '');
    $father = $_POST['sortlevel'] ?? '';
    if ($name === '') { exit('分類名不可為空'); }
    $cat->add($name, $father);
    header('Location: cat.php');
    exit;
}

if (($_GET['action'] ?? '') === 'tpl') {
    // 新增表單:下拉選父級
    echo '<form method="post" action="cat.php?action=add">';
    echo '<select name="sortlevel"><option value="">根分類</option>';
    foreach ($cat->getOptions() as $level => $label) {
        echo "<option value=\"$level\">$label</option>";
    }
    echo '</select> <input name="sortname" required> <button>新增</button></form>';
    exit;
}

// 默認:顯示樹狀列表
foreach ($cat->getAll() as $v) {
    $depth = strlen($v['sortlevel']) / 3 - 1;
    echo str_repeat('— ', $depth) . htmlspecialchars($v['sortname']) . "<br>\n";
}

性能與擴展性分析

指標表現
讀取 1 萬分類include + uksort ≈ 3-5 ms(OPcache 後)
新增分類陣列插入 + 文件寫入 ≈ 1-2 ms
記憶體佔用約 1 萬分類 ≈ 2-3 MB
遞歸深度限制無(非遞歸算法)
併發寫入建議加 flock 或改用 SQLite/Redis

適用場景與遷移建議

  • ✅ 適合:中小型 CMS 欄目、標籤系統、導航選單、靜態站點生成器
  • ❌ 不適合:高併發寫入、多伺服器集群、需複雜 SQL 聯查、分類超 10 萬節點
  • 🔄 遷移:將 data.php 導入資料庫 categories(sortlevel, sortname),sortlevel 存 VARCHAR(64) 並建索引,原有算法邏輯完全復用

總結

用 sortlevel 編碼 + 陣列字典序 + 文件緩存,實現了零資料庫、零遞歸、極簡代碼的無限分類。理解其核心思想——把樹結構編碼成可排序字串——你就能在任何語言、任何存儲介質上複刻這套高效方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved