【问题标题】:stuck with multi-level categories and sub-categories php mysql坚持多级类别和子类别php mysql
【发布时间】:2022-11-20 01:46:04
【问题描述】:

我确实有一些产品目录。目前我可以列出类别(level1)和子类别(level2)。 我想添加一个可能是子子类别(level3)的第三级。 添加有效,但列表(循环)不起作用,因为它限制了我的 2 个级别。

我需要一些帮助来解决这个问题。

这是添加和列出的代码,mysql 结构是:

CREATE TABLE `categories` (
  `lcat_id` smallint(5) UNSIGNED NOT NULL,
  `lcat_name` varchar(255) NOT NULL DEFAULT '',
  `lcat_path` varchar(255) NOT NULL DEFAULT '',
  `sub_cat` int(11) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

我的代码是:

<?php
<form action="<?php echo $_SERVER["PHP_SELF"]; ?>?action=newcat" method ="POST"> 
Category Name : <input type = "text" name = "cat_name" maxlength="250"><br><br>
Sub of category : <select name="lcat_id">
<option value=''>0</option>
<?php
$sql_cat = mysqli_query($db_connect,"SELECT * from ".TABLE_CATS." WHERE sub_cat='0' ORDER BY lcat_id") or die(mysqli_error($db_connect));
        while($row_cat = mysqli_fetch_array($sql_cat)) {
        extract($row_cat);
        /*
        $id = $row_cat["lcat_id"];
        $name = $row_cat["lcat_name"];
        $sub_cat = $row_cat["sub_cat"];
        */
        echo "<option  value='".$row_cat["lcat_id"]."'>".$row_cat['lcat_name']."</option>";
            $sql_subcat = mysqli_query($db_connect,"SELECT * from ".TABLE_CATS." WHERE sub_cat='".$row_cat["lcat_id"]."' ") or die(mysqli_error($db_connect));
                while($row_subcat = mysqli_fetch_array($sql_subcat)) {
                extract($row_subcat);
                /*
                $id = $row_subcat["lcat_id"];
                $namea = $row_subcat["lcat_name"];
                $sub_cata = $row_subcat["sub_cat"];
                */
                echo "<option  value='".$row_subcat['lcat_id']."'>-> ".$row_subcat['lcat_name']."</option>";
                }
    }   
?>
</select>
<input type = "submit" value = "New Category"><br>
</form>
<?php
if ($_GET['action']=="newcat"){
        if ($_POST['cat_name']==""){
            print "You did not put anything.<br/><a href=".$_SERVER["PHP_SELF"].">Go back</a>";
            
        }

        $sql_cat = mysqli_query($db_connect,"SELECT lcat_name from ".TABLE_CATS." WHERE lcat_name='".$_POST['cat_name']."' ") or die(mysqli_error($db_connect));
            while($row_cat = mysqli_fetch_array($sql_cat)) {
            extract($row_cat);
            $name = $row_cat["lcat_name"];
            }
            if ($row_cat['cat_name']){
                print "Category <b>".$_POST['cat_name']."</b> already exists in <b>".$mysql_db."</b>.<br>Please chose new name.<br/><a href=".$_SERVER["PHP_SELF"].">Go back</a>";
                
            }

            $sql_subcat = mysqli_query($db_connect,"SELECT * from ".TABLE_CATS." WHERE sub_cat='".$row_cat['lcat_id']."' AND lcat_name='".$row_cat["lcat_name"]."'") or die(mysqli_error($db_connect));
                while($row_subcat = mysqli_fetch_array($sql_subcat)) {
                extract($row_subcat);
                $namea = $row_subcat["lcat_name"];
                $sub_cat = $row_subcat["sub_cat"];
        }

        if ($row_subcat['lcat_name']) {
            echo ("Sub-category <b>".$row_subcat['lcat_name']."</b> already exists in <b>$mysql_db</b>. Please chose new name.<br/><a href=".$_SERVER["PHP_SELF"].">Go back</a>");
            exit;
        } else {
            $sql_query = "INSERT INTO ".TABLE_CATS." (lcat_name, lcat_path, sub_cat) VALUES ('".$_POST['cat_name']."','".$_POST['cat_name']."','".$_POST['lcat_id']."')";
            $result = mysqli_query($db_connect,$sql_query);
            echo "You added category :<b> ".$_POST['cat_name']."</b> in <b>$mysql_db</b><br/><a href=".$_SERVER["PHP_SELF"].">Go back</a>.";
        }


}
?>

【问题讨论】:

  • 如果您创建一个传入起始类别并返回所有子类别的函数,您可以根据需要调用它。
  • 请举个例子,这样我就可以开始了?
  • sub_cat是指parent_id吗?你希望lcat_path有什么?您在此类别表上缺少 PK。
  • lcat_path 基本上是 url slug ,它只用于 seo。我没有 parent_ID,我正在使用 sub_cat 信息,
  • 那么sub_cat 是表示当前类别的父级的列吗?它没有对lcat_id 的任何外键引用?也许(重新)访问How to Ask 会是最好的。这个问题肯定需要稍微重组一下,这样它的各个部分的意思就很清楚了。

标签: php mysql


【解决方案1】:

我将从改进您的类别表开始 -

  • 添加主键
  • 为 parent_id 添加外键约束
  • 删除冗余路径列
  • 并删除列名称前缀(该表已称为类别)
CREATE TABLE `categories` (
    `id` SMALLINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(255) NOT NULL,
    `parent_id` SMALLINT UNSIGNED,
    CONSTRAINT `fk_parent_category` FOREIGN KEY (parent_id) REFERENCES categories (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

我不会使用嵌套循环运行查询来获取子类别或递归函数,而是使用递归 Common Table Expression (CTE) 来构建和返回类别层次结构 -

WITH RECURSIVE nested_cats AS (
    SELECT *, 0 `depth`, CAST(`name` AS CHAR(200)) AS `path`
    FROM `categories`
    WHERE `parent_id` IS NULL
    UNION ALL
    SELECT `c`.*, `nc`.`depth` + 1, CONCAT(`nc`.`path`, ' > ', `c`.`name`)
    FROM `categories` `c`
    JOIN `nested_cats` `nc` ON `nc`.`id` = `c`.`parent_id`
)
SELECT * FROM `nested_cats` ORDER BY `path`;

在您当前的代码中,您通过在没有任何验证的情况下将字符串与用户输入连接起来来构建您的 SQL。这使您的脚本很容易受到 SQL Injection 的攻击。您应该花一些时间来了解prepared statements 以及它们如何帮助您防止 SQLi。您当前使用的是 MySQLi,它提供对准备语句和参数化的支持,但对于我的示例,我使用的是 PDO,因为我发现它更直观。

<?php

// During development, report all PHP errors
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

$pdo = new PDO('mysql:dbname=test;host=localhost', 'db_user', 'db_pass');

$feedback = '';
$action = filter_input(INPUT_POST, 'action');

/* IF IS POST DO ADD */
if ($action == 'New Category'){
    $cat_name = filter_input(INPUT_POST, 'cat_name', FILTER_SANITIZE_SPECIAL_CHARS);
    if (empty($cat_name)){
        $feedback = 'You did not put anything.';
    } else {

        $parent_id = filter_input(INPUT_POST, 'parent_id', FILTER_VALIDATE_INT, ['options' => ['default' => null]]);

        // check to see if it already exists
        $statement = $pdo->prepare('SELECT `name` FROM `categories` WHERE `name` = :name');

        // execute the prepared statement
        $statement->execute(['name' => $cat_name]);

        // retrieve result rows
        $rows = $statement->fetchAll(PDO::FETCH_OBJ);

        if ($rows) {
            $feedback = "Category {$rows[0]->name} already exists!";
        } else {
            $stmt = $pdo->prepare('INSERT INTO `categories` (`name`, `parent_id`) VALUES (:name, :parent_id)');
            $stmt->execute([':name' => $cat_name, ':parent_id' => $parent_id]);
            $feedback =  "You added category: <b>{$cat_name}</b>.";
        }
    }
}

/* LOAD ALL CATEGORIES */
$sql = '
    WITH RECURSIVE `nested_cats` AS (
        SELECT *, 0 `depth`, CAST(`name` AS CHAR(200)) AS `path`
        FROM `categories`
        WHERE `parent_id` IS NULL
        UNION ALL
        SELECT c.*, `nc`.`depth` + 1, CONCAT(`nc`.`path`, " > ", `c`.`name`)
        FROM `categories` `c`
        JOIN `nested_cats` `nc` ON `nc`.`id` = `c`.`parent_id`
    )
    SELECT * FROM `nested_cats` ORDER BY `path`';
$result = $pdo->query($sql);
$categories = $result->fetchAll(PDO::FETCH_OBJ);
?>

<form action="" method ="POST">
Category Name : <input type="text" name="cat_name" maxlength="250"><br><br>
Sub of category : <select name="parent_id">
<option value=""> - </option>
<?php

foreach ($categories as $category) {
    $indent = str_repeat('-> ', $category->depth);
    echo "<option  value='{$category->id}'>{$indent}{$category->name}</option>
";
}

?>
</select>
<input type="submit" name="action" value="New Category"><br>
</form>

<div><?php echo $feedback; ?></div>

这只是一个粗略的例子,没有错误检查/处理,但它应该能让你朝着更好的方向前进。

【讨论】:

    猜你喜欢
    • 2011-06-05
    • 2010-11-22
    • 1970-01-01
    • 2011-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多