36、PHP 开发:笑话网站的功能实现与用户权限管理

PHP 开发:笑话网站的功能实现与用户权限管理

1. 数据库操作与实体对象处理

在数据库操作中,对于新创建的记录,其主键(如 id )是由 MySQL 在数据库内部生成的。在插入记录后,需要从数据库中读取该主键值。PDO 库提供了 lastInsertId 方法来实现这一功能。

以下是修改 DatabaseTable 类的 insert 方法以返回最后插入的 ID 的代码:

private function insert($fields) {
    $query = 'INSERT INTO `' . $this->table . '` (';
    foreach ($fields as $key => $value) {
        $query .= '`' . $key . '`,';
    }
    $query = rtrim($query, ',');
    $query .= ') VALUES (';
    foreach ($fields as $key => $value) {
        $query .= ':' . $key . ',';
    }
    $query = rtrim($query, ',');
    $query .= ')';
    $fields = $this->processDates($fields);
    $this->query($query, $fields);
    return $this->pdo->lastInsertId();
}

修改后的 save 方法可以读取该值并设置创建的实体对象的主键:

public function save($record) {
    $entity = new $this->className(...$this->constructorArgs);
    try {
        if ($record[$this->primaryKey] == '') {
            $record[$this->primaryKey] = null;
        }
        $insertId = $this->insert($record);
        $entity->{$this->primaryKey} = $insertId;
    }
    catch (\PDOException $e) {
        $this->update($record);
    }
    foreach ($record as $key => $value) {
        $entity->$key = $value;
    }
    return $entity;
}
2. 为笑话分配类别

为了让笑话控制器能够为笑话实例分配类别,需要对 Joke 控制器的 saveEdit 方法进行修改:

public function saveEdit() {
    $author = $this->authentication->getUser();
    $joke = $_POST['joke'];
    $joke['jokedate'] = new \DateTime();
    $jokeEntity = $author->addJoke($joke);
    foreach ($_POST['category'] as $categoryId) {
        $jokeEntity->addCategory($categoryId);
    }
    header('location: /joke/list');
}

同时,需要对 Joke 实体类进行修改,添加 addCategory 方法,并在构造函数中引入 jokeCategoriesTable 实例:

<?php
namespace Ijdb\Entity;
class Joke {
    public $id;
    public $authorId;
    public $jokedate;
    public $joketext;
    private $authorsTable;
    private $author;
    private $jokeCategoriesTable;
    public function __construct(\Ninja\DatabaseTable $authorsTable, \Ninja\DatabaseTable $jokeCategoriesTable) {
        $this->authorsTable = $authorsTable;
        $this->jokeCategoriesTable = $jokeCategoriesTable;
    }
    public function addCategory($categoryId) {
        $jokeCat = ['jokeId' => $this->id, 'categoryId' => $categoryId];
        $this->jokeCategoriesTable->save($jokeCat);
    }
}

并且在 IjdbRoutes 中为 $jokesTable 实例的构造函数提供 jokeCategoriesTable 实例:

$this->jokesTable = new \Ninja\DatabaseTable($pdo, 'joke', 'id', '\Ijdb\Entity\Joke', [&$this->authorsTable, &$this->jokeCategoriesTable]);
3. 按类别显示笑话

在笑话列表页面添加类别列表以实现笑话过滤功能,具体步骤如下:
1. 修改 list 方法,将类别列表传递给模板:

public function list() {
    $jokes = $this->jokesTable->findAll();
    $title = 'Joke list';
    $totalJokes = $this->jokesTable->total();
    $author = $this->authentication->getUser();
    return [
        'template' => 'jokes.html.php',
        'title' => $title,
        'variables' => [
            'totalJokes' => $totalJokes,
            'jokes' => $jokes,
            'userId' => $author->id ?? null,
            'categories' => $this->categoriesTable->findAll()
        ]
    ];
}
  1. jokes.html.php 模板中循环遍历类别并创建链接列表:
<div class="jokelist">
    <ul class="categories">
        <?php foreach ($categories as $category): ?>
            <li><a href="/joke/list?category=<?=$category->id?>"><?=$category->name?></a></li>
        <?php endforeach; ?>
    </ul>
    <div class="jokes">
        <p><?=$totalJokes?> jokes have been submitted to the Internet Joke Database.</p>
        <?php foreach ($jokes as $joke): ?>
            // …
        <?php endforeach; ?>
    </div>
</div>
  1. 应用 CSS 样式:
.jokelist {display: table;}
.categories {display: table-cell; width: 20%; background-color: #333; padding: 1em; list-style-type: none;}
.categories a {color: white; text-decoration: none;}
.categories li {margin-bottom: 1em;}
.jokelist .jokes {display: table-cell; padding: 1em;}

当点击类别链接时,需要根据 $_GET 变量显示过滤后的笑话列表。由于是多对多关系,不能简单地通过 categoryId 进行查询。可以创建 Category 实体类,添加 getJokes 方法:

<?php
namespace Ijdb\Entity;
use Ninja\DatabaseTable;
class Category {
    public $id;
    public $name;
    private $jokesTable;
    private $jokeCategoriesTable;
    public function __construct(DatabaseTable $jokesTable, DatabaseTable $jokeCategoriesTable) {
        $this->jokesTable = $jokesTable;
        $this->jokeCategoriesTable = $jokeCategoriesTable;
    }
    public function getJokes() {
        $jokeCategories = $this->jokeCategoriesTable->find('categoryId', $this->id);
        $jokes = [];
        foreach ($jokeCategories as $jokeCategory) {
            $joke =  $this->jokesTable->findById($jokeCategory->jokeId);
            if ($joke) {
                $jokes[] = $joke;
            }
        }
        return $jokes;
    }
}

IjdbRoutes 中设置 categoriesTable 实例使用新的 Category 实体类:

$this->categoriesTable = new \Ninja\DatabaseTable($pdo, 'category', 'id', '\Ijdb\Entity\Category', [&$this->jokesTable, &$this->jokeCategoriesTable]);

最后,在 list 控制器动作中使用新的 getJokes 方法:

if (isset($_GET['category'])) {
    $category = $this->categoriesTable->findById($_GET['category']);
    $jokes = $category->getJokes();
} else {
    $jokes = $this->jokesTable->findAll();
}

以下是按类别显示笑话的流程图:

graph TD;
    A[用户访问笑话列表页面] --> B[显示类别列表和所有笑话];
    B --> C{用户点击类别链接};
    C -- 是 --> D[根据类别 ID 获取笑话列表];
    C -- 否 --> B;
    D --> E[显示过滤后的笑话列表];
4. 编辑笑话

在编辑笑话时,会遇到两个问题:
1. 复选框未选中问题 :需要在 Joke 实体类中添加 hasCategory 方法,用于判断笑话是否属于某个类别:

public function hasCategory($categoryId) {
    $jokeCategories = $this->jokeCategoriesTable->find('jokeId', $this->id);
    foreach ($jokeCategories as $jokeCategory) {
        if ($jokeCategory->categoryId == $categoryId) {
            return true;
        }
    }
}

editjoke.html.php 模板中使用该方法来检查复选框:

<p>Select categories for this joke:</p>
<?php foreach ($categories as $category): ?>
    <?php if ($joke && $joke->hasCategory($category->id)): ?>
        <input type="checkbox" checked name="category[]" value="<?=$category->id?>" />
    <?php else: ?>
        <input type="checkbox" name="category[]" value="<?=$category->id?>" />
    <?php endif; ?>
    <label><?=$category->name?></label>
<?php endforeach; ?>
  1. 类别修改保存问题 :为了解决这个问题,需要在 DatabaseTable 类中添加 deleteWhere 方法,用于根据条件删除记录:
public function deleteWhere($column, $value) {
    $query = 'DELETE FROM ' . $this->table . ' WHERE ' . $column . ' = :value';
    $parameters = ['value' => $value];
    $query = $this->query($query, $parameters);
}

Joke 实体类中添加 clearCategories 方法,用于清除笑话的所有类别:

public function clearCategories() {
    $this->jokeCategoriesTable->deleteWhere('jokeId', $this->id);
}

并在 Joke 控制器的 saveEdit 方法中调用该方法:

public function saveEdit() {
    $author = $this->authentication->getUser();
    $joke = $_POST['joke'];
    $joke['jokedate'] = new \DateTime();
    $jokeEntity = $author->addJoke($joke);
    $jokeEntity->clearCategories();
    foreach ($_POST['category'] as $categoryId) {
        $jokeEntity->addCategory($categoryId);
    }
    header('location: /joke/list');
}
5. 用户角色与权限管理

为了实现不同用户的不同操作权限,需要设置用户角色。在这个笑话网站中,至少需要以下两种访问级别:
| 用户角色 | 权限 |
| ---- | ---- |
| 标准用户 | 发布新笑话,编辑/删除自己发布的笑话 |
| 管理员 | 添加/编辑/删除类别,发布笑话,编辑/删除任何人发布的笑话,将其他用户转为管理员 |

可以在 author 表中添加一个列来表示用户的访问级别,例如 1 表示普通用户, 2 表示管理员。通过以下代码检查用户是否为管理员:

$author = $this->authentication->getUser();
if ($author->accessLevel == 2) {
    // They're an administrator
} else {
    // Otherwise, they're not
}

为了提高代码的可读性,可以将权限检查抽象为方法,例如 isAdmin()

对于大型网站,更灵活的方法是为每个用户的每个操作分配单独的权限。可以在 Author 实体类中定义常量来表示不同的权限:

<?php
namespace Ijdb\Entity;
class Author {
    const EDIT_JOKES = 1;
    const DELETE_JOKES = 2;
    // 其他权限常量...
}

并在 Author 实体类中添加 hasPermission 方法来检查用户是否具有特定权限。

通过以上步骤,可以实现一个功能完善的笑话网站,包括数据库操作、类别分配、按类别显示笑话、编辑笑话以及用户权限管理等功能。

PHP 开发:笑话网站的功能实现与用户权限管理

6. 权限检查方法的实现

为了更方便地检查用户是否具有特定权限,在 Author 实体类中添加 hasPermission 方法:

<?php
namespace Ijdb\Entity;
class Author {
    const EDIT_JOKES = 1;
    const DELETE_JOKES = 2;
    // 其他权限常量...

    public function hasPermission($permission) {
        // 假设权限存储在一个数组中,这里需要根据实际情况修改
        $permissions = $this->getPermissions(); 
        return in_array($permission, $permissions);
    }

    private function getPermissions() {
        // 这里需要根据数据库或其他存储方式获取用户的权限列表
        // 示例代码,需要根据实际情况修改
        return [/* 用户的权限列表 */]; 
    }
}

这样,在需要检查用户权限的地方,可以使用 $author->hasPermission(Author::EDIT_JOKES) 来判断用户是否具有编辑笑话的权限。

7. 权限管理的应用场景

以下是权限管理在不同场景下的应用示例:

7.1 类别管理页面

在类别管理页面,只有管理员才能进行添加、编辑和删除类别操作。可以在控制器中添加权限检查:

public function manageCategories() {
    $author = $this->authentication->getUser();
    if (!$author->hasPermission(Author::MANAGE_CATEGORIES)) {
        // 没有权限,重定向到其他页面或显示错误信息
        header('location: /joke/list');
        exit;
    }
    // 管理员可以进行的操作
    // ...
}
7.2 笑话编辑和删除操作

在编辑和删除笑话时,需要检查用户是否具有相应的权限。对于普通用户,只能编辑和删除自己发布的笑话;对于管理员,可以编辑和删除任何人发布的笑话。

public function editJoke($jokeId) {
    $author = $this->authentication->getUser();
    $joke = $this->jokesTable->findById($jokeId);
    if (!$author->hasPermission(Author::EDIT_JOKES) || ($author->id != $joke->authorId &&!$author->isAdmin())) {
        // 没有权限,重定向到其他页面或显示错误信息
        header('location: /joke/list');
        exit;
    }
    // 可以进行编辑操作
    // ...
}

public function deleteJoke($jokeId) {
    $author = $this->authentication->getUser();
    $joke = $this->jokesTable->findById($jokeId);
    if (!$author->hasPermission(Author::DELETE_JOKES) || ($author->id != $joke->authorId &&!$author->isAdmin())) {
        // 没有权限,重定向到其他页面或显示错误信息
        header('location: /joke/list');
        exit;
    }
    // 可以进行删除操作
    // ...
}
8. 数据库表结构的调整

为了实现用户权限管理,需要对数据库表结构进行调整。在 author 表中添加 accessLevel 列来表示用户的访问级别,同时可以创建一个新的 author_permission 表来存储用户的具体权限。

author 表结构示例:
| 列名 | 类型 | 描述 |
| ---- | ---- | ---- |
| id | int | 作者 ID,主键 |
| name | varchar | 作者姓名 |
| email | varchar | 作者邮箱 |
| password | varchar | 作者密码 |
| accessLevel | int | 用户访问级别,1 表示普通用户,2 表示管理员 |

author_permission 表结构示例:
| 列名 | 类型 | 描述 |
| ---- | ---- | ---- |
| id | int | 权限记录 ID,主键 |
| authorId | int | 作者 ID,外键关联 author 表的 id |
| permission | int | 权限代码,对应 Author 类中的常量 |

9. 权限管理的流程图
graph TD;
    A[用户请求操作] --> B{检查用户权限};
    B -- 有权限 --> C[执行操作];
    B -- 无权限 --> D[显示错误信息或重定向];
    C --> E[操作完成];
10. 总结

通过以上一系列的操作,我们实现了一个功能丰富且安全的笑话网站。从数据库操作、实体对象处理,到笑话的类别分配、按类别显示和编辑,再到用户角色与权限管理,每个环节都紧密相连,确保了网站的正常运行和数据的安全性。

在开发过程中,我们使用了面向对象的编程思想,将不同的功能封装到不同的类和方法中,提高了代码的可维护性和可扩展性。同时,通过合理的数据库设计和权限管理,保障了用户数据的安全和操作的合法性。

在实际应用中,可以根据具体需求对网站进行进一步的扩展和优化,例如添加更多的权限类型、优化界面设计等。希望以上内容对 PHP 开发者在构建类似的网站时有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值