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()
]
];
}
-
在
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>
- 应用 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; ?>
-
类别修改保存问题
:为了解决这个问题,需要在
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 开发者在构建类似的网站时有所帮助。
超级会员免费看
7

被折叠的 条评论
为什么被折叠?



