这一章将带领你从零开始,逐步创建一个功能完备的博客系统。这不仅是对前面章节所学内容的实践和巩固,也是一次宝贵的项目经验积累。通过这个项目,你将深入理解 PHP 的应用,并掌握开发一个完整项目的流程和技巧。

项目介绍与规划

项目需求分析与功能规划

在开始编码之前,明确项目的需求和功能非常重要。我们将实现一个简单但功能齐全的博客系统,包括以下功能:

  1. 用户注册与登录
  2. 文章的创建、编辑、删除和展示
  3. 评论功能
  4. 文章的分页展示
  5. 权限管理(仅作者可以编辑和删除自己的文章)
  6. 部署到生产环境

项目目录结构的设计

一个清晰的目录结构有助于项目的维护和扩展。以下是我们博客系统的目录结构:

  1. blog/
  2. ├── config/ # 配置文件
  3. └── config.php
  4. ├── public/ # 公开访问的文件
  5. ├── index.php
  6. └── css/
  7. └── styles.css
  8. ├── src/ # 核心代码
  9. ├── Controllers/
  10. ├── ArticleController.php
  11. └── UserController.php
  12. ├── Models/
  13. ├── Article.php
  14. └── User.php
  15. ├── Views/
  16. ├── articles/
  17. ├── create.php
  18. ├── edit.php
  19. └── index.php
  20. └── users/
  21. ├── login.php
  22. └── register.php
  23. └── Helpers/
  24. └── Auth.php
  25. ├── tests/ # 测试代码
  26. ├── vendor/ # 第三方库
  27. └── .htaccess # Apache 配置

用户注册与登录

实现用户注册功能

数据库设计

首先,我们需要在数据库中创建一个用于存储用户信息的表。以下是一个简单的 users 表的 SQL 创建语句:

  1. CREATE TABLE users (
  2. id INT AUTO_INCREMENT PRIMARY KEY,
  3. username VARCHAR(50) NOT NULL UNIQUE,
  4. email VARCHAR(100) NOT NULL UNIQUE,
  5. password VARCHAR(255) NOT NULL,
  6. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  7. );

用户注册表单

接下来,我们在 Views/users/register.php 文件中创建用户注册表单:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>用户注册</title>
  5. </head>
  6. <body>
  7. <h2>注册新用户</h2>
  8. <form action="/register" method="POST">
  9. <label for="username">用户名:</label>
  10. <input type="text" id="username" name="username" required><br>
  11. <label for="email">电子邮箱:</label>
  12. <input type="email" id="email" name="email" required><br>
  13. <label for="password">密码:</label>
  14. <input type="password" id="password" name="password" required><br>
  15. <input type="submit" value="注册">
  16. </form>
  17. </body>
  18. </html>

处理用户注册请求

Controllers/UserController.php 中编写处理用户注册请求的逻辑:

  1. <?php
  2. namespace Controllers;
  3. use Models\User;
  4. use Helpers\Auth;
  5. class UserController
  6. {
  7. public function register()
  8. {
  9. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  10. $username = $_POST['username'];
  11. $email = $_POST['email'];
  12. $password = password_hash($_POST['password'], PASSWORD_BCRYPT);
  13. $user = new User();
  14. $user->username = $username;
  15. $user->email = $email;
  16. $user->password = $password;
  17. $user->save();
  18. header('Location: /login');
  19. } else {
  20. require 'Views/users/register.php';
  21. }
  22. }
  23. }

用户模型

Models/User.php 中定义 User 类:

  1. <?php
  2. namespace Models;
  3. use PDO;
  4. class User
  5. {
  6. public $id;
  7. public $username;
  8. public $email;
  9. public $password;
  10. public $created_at;
  11. public function save()
  12. {
  13. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  14. $sql = "INSERT INTO users (username, email, password) VALUES (:username, :email, :password)";
  15. $stmt = $pdo->prepare($sql);
  16. $stmt->execute([
  17. ':username' => $this->username,
  18. ':email' => $this->email,
  19. ':password' => $this->password
  20. ]);
  21. }
  22. }

实现用户登录功能

用户登录表单

Views/users/login.php 文件中创建用户登录表单:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>用户登录</title>
  5. </head>
  6. <body>
  7. <h2>用户登录</h2>
  8. <form action="/login" method="POST">
  9. <label for="username">用户名:</label>
  10. <input type="text" id="username" name="username" required><br>
  11. <label for="password">密码:</label>
  12. <input type="password" id="password" name="password" required><br>
  13. <input type="submit" value="登录">
  14. </form>
  15. </body>
  16. </html>

处理用户登录请求

Controllers/UserController.php 中添加处理用户登录请求的逻辑:

  1. <?php
  2. namespace Controllers;
  3. use Models\User;
  4. use Helpers\Auth;
  5. class UserController
  6. {
  7. public function login()
  8. {
  9. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  10. $username = $_POST['username'];
  11. $password = $_POST['password'];
  12. $user = User::findByUsername($username);
  13. if ($user && password_verify($password, $user->password)) {
  14. Auth::login($user);
  15. header('Location: /dashboard');
  16. } else {
  17. echo "用户名或密码错误";
  18. }
  19. } else {
  20. require 'Views/users/login.php';
  21. }
  22. }
  23. }

用户认证与权限管理

Helpers/Auth.php 中编写用户认证和权限管理的辅助函数:

  1. <?php
  2. namespace Helpers;
  3. use Models\User;
  4. class Auth
  5. {
  6. public static function login($user)
  7. {
  8. session_start();
  9. $_SESSION['user_id'] = $user->id;
  10. }
  11. public static function logout()
  12. {
  13. session_start();
  14. session_destroy();
  15. }
  16. public static function user()
  17. {
  18. session_start();
  19. if (isset($_SESSION['user_id'])) {
  20. return User::findById($_SESSION['user_id']);
  21. }
  22. return null;
  23. }
  24. public static function check()
  25. {
  26. session_start();
  27. return isset($_SESSION['user_id']);
  28. }
  29. }

文章管理

创建与编辑文章

数据库设计

首先,为文章创建一个数据库表。以下是 articles 表的 SQL 创建语句:

  1. CREATE TABLE articles (
  2. id INT AUTO_INCREMENT PRIMARY KEY,
  3. title VARCHAR(255) NOT NULL,
  4. content TEXT NOT NULL,
  5. user_id INT NOT NULL,
  6. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  7. FOREIGN KEY (user_id) REFERENCES users(id)
  8. );

文章创建表单

Views/articles/create.php 文件中创建文章创建表单:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>创建文章</title>
  5. </head>
  6. <body>
  7. <h2>创建新文章</h2>
  8. <form action="/articles/create" method="POST">
  9. <label for="title">标题:</label>
  10. <input type="text" id="title" name="title" required><br>
  11. <label for="content">内容:</label>
  12. <textarea id="content" name="content" required></textarea><br>
  13. <input type="submit" value="发布">
  14. </form>
  15. </body>
  16. </html>

处理文章创建请求

Controllers/ArticleController.php 中编写处理文章创建请求的逻辑:

  1. <?php
  2. namespace Controllers;
  3. use Models\Article;
  4. use Helpers\Auth;
  5. class ArticleController
  6. {
  7. public function create()
  8. {
  9. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  10. $title = $_POST['title'];
  11. $content = $_POST['content'];
  12. $user_id = Auth::user()->id;
  13. $article = new Article();
  14. $article->title = $title;
  15. $article->content = $content;
  16. $article->user_id = $user_id;
  17. $article->save();
  18. header('Location: /articles');
  19. } else {
  20. require 'Views/articles/create.php';
  21. }
  22. }
  23. }

文章模型

Models/Article.php 中定义 Article 类:

  1. <?php
  2. namespace Models;
  3. use PDO;
  4. class Article
  5. {
  6. public $id;
  7. public $# 第九章:项目实战:创建一个博客系统 (续)
  8. 这一章继续带领你完成创建一个博客系统的实战项目,包括用户认证、文章管理和评论功能的实现。
  9. ## 文章管理 (续)
  10. ### 创建与编辑文章 (续)
  11. #### 文章模型 (续)
  12. `Models/Article.php` 中定义 `Article` 类:
  13. ```php
  14. <?php
  15. namespace Models;
  16. use PDO;
  17. class Article
  18. {
  19. public $id;
  20. public $title;
  21. public $content;
  22. public $user_id;
  23. public $created_at;
  24. public function save()
  25. {
  26. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  27. $sql = "INSERT INTO articles (title, content, user_id) VALUES (:title, :content, :user_id)";
  28. $stmt = $pdo->prepare($sql);
  29. $stmt->execute([
  30. ':title' => $this->title,
  31. ':content' => $this->content,
  32. ':user_id' => $this->user_id
  33. ]);
  34. }
  35. public static function findById($id)
  36. {
  37. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  38. $sql = "SELECT * FROM articles WHERE id = :id";
  39. $stmt = $pdo->prepare($sql);
  40. $stmt->execute([':id' => $id]);
  41. return $stmt->fetchObject(self::class);
  42. }
  43. }

编辑文章功能

文章编辑表单

Views/articles/edit.php 文件中创建文章编辑表单:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>编辑文章</title>
  5. </head>
  6. <body>
  7. <h2>编辑文章</h2>
  8. <form action="/articles/edit" method="POST">
  9. <input type="hidden" name="id" value="<?php echo $article->id; ?>">
  10. <label for="title">标题:</label>
  11. <input type="text" id="title" name="title" value="<?php echo $article->title; ?>" required><br>
  12. <label for="content">内容:</label>
  13. <textarea id="content" name="content" required><?php echo $article->content; ?></textarea><br>
  14. <input type="submit" value="更新">
  15. </form>
  16. </body>
  17. </html>

处理文章编辑请求

Controllers/ArticleController.php 中添加处理文章编辑请求的逻辑:

  1. <?php
  2. namespace Controllers;
  3. use Models\Article;
  4. class ArticleController
  5. {
  6. public function edit()
  7. {
  8. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  9. $id = $_POST['id'];
  10. $title = $_POST['title'];
  11. $content = $_POST['content'];
  12. $article = Article::findById($id);
  13. $article->title = $title;
  14. $article->content = $content;
  15. $article->save();
  16. header('Location: /articles');
  17. } else {
  18. $id = $_GET['id'];
  19. $article = Article::findById($id);
  20. require 'Views/articles/edit.php';
  21. }
  22. }
  23. }

文章的展示与分页

文章展示页面

Views/articles/index.php 文件中创建文章展示页面:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>文章列表</title>
  5. </head>
  6. <body>
  7. <h2>文章列表</h2>
  8. <a href="/articles/create">创建新文章</a>
  9. <ul>
  10. <?php foreach ($articles as $article): ?>
  11. <li>
  12. <a href="/articles/show?id=<?php echo $article->id; ?>"><?php echo $article->title; ?></a>
  13. <a href="/articles/edit?id=<?php echo $article->id; ?>">编辑</a>
  14. <a href="/articles/delete?id=<?php echo $article->id; ?>" onclick="return confirm('确定要删除吗?');">删除</a>
  15. </li>
  16. <?php endforeach; ?>
  17. </ul>
  18. </body>
  19. </html>

处理文章展示请求

Controllers/ArticleController.php 中添加处理文章展示请求的逻辑:

  1. <?php
  2. namespace Controllers;
  3. use Models\Article;
  4. class ArticleController
  5. {
  6. public function index()
  7. {
  8. $articles = Article::all();
  9. require 'Views/articles/index.php';
  10. }
  11. public function show()
  12. {
  13. $id = $_GET['id'];
  14. $article = Article::findById($id);
  15. require 'Views/articles/show.php';
  16. }
  17. public function delete()
  18. {
  19. $id = $_GET['id'];
  20. $article = Article::findById($id);
  21. $article->delete();
  22. header('Location: /articles');
  23. }
  24. }

文章分页

为了实现文章的分页展示,我们可以在 Article 类中添加分页方法:

  1. <?php
  2. namespace Models;
  3. use PDO;
  4. class Article
  5. {
  6. // ... 其他代码
  7. public static function paginate($limit, $offset)
  8. {
  9. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  10. $sql = "SELECT * FROM articles LIMIT :limit OFFSET :offset";
  11. $stmt = $pdo->prepare($sql);
  12. $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
  13. $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
  14. $stmt->execute();
  15. return $stmt->fetchAll(PDO::FETCH_CLASS, self::class);
  16. }
  17. public static function count()
  18. {
  19. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  20. $sql = "SELECT COUNT(*) as count FROM articles";
  21. $stmt = $pdo->query($sql);
  22. return $stmt->fetch(PDO::FETCH_ASSOC)['count'];
  23. }
  24. }

ArticleController.php 中修改 index 方法以支持分页:

  1. <?php
  2. namespace Controllers;
  3. use Models\Article;
  4. class ArticleController
  5. {
  6. public function index()
  7. {
  8. $page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
  9. $limit = 5;
  10. $offset = ($page - 1) * $limit;
  11. $articles = Article::paginate($limit, $offset);
  12. $total = Article::count();
  13. $pages = ceil($total / $limit);
  14. require 'Views/articles/index.php';
  15. }
  16. // ... 其他代码
  17. }

Views/articles/index.php 中添加分页导航:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>文章列表</title>
  5. </head>
  6. <body>
  7. <h2>文章列表</h2>
  8. <a href="/articles/create">创建新文章</a>
  9. <ul>
  10. <?php foreach ($articles as $article): ?>
  11. <li>
  12. <a href="/articles/show?id=<?php echo $article->id; ?>"><?php echo $article->title; ?></a>
  13. <a href="/articles/edit?id=<?php echo $article->id; ?>">编辑</a>
  14. <a href="/articles/delete?id=<?php echo $article->id; ?>" onclick="return confirm('确定要删除吗?');">删除</a>
  15. </li>
  16. <?php endforeach; ?>
  17. </ul>
  18. <div>
  19. <?php for ($i = 1; $i <= $pages; $i++): ?>
  20. <a href="/articles?page=<?php echo $i; ?>"><?php echo $i; ?></a>
  21. <?php endfor; ?>
  22. </div>
  23. </body>
  24. </html>

评论功能的实现

数据库设计

为评论创建一个数据库表。以下是 comments 表的 SQL 创建语句:

  1. CREATE TABLE comments (
  2. id INT AUTO_INCREMENT PRIMARY KEY,
  3. article_id INT NOT NULL,
  4. user_id INT NOT NULL,
  5. content TEXT NOT NULL,
  6. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  7. FOREIGN KEY (article_id) REFERENCES articles(id),
  8. FOREIGN KEY (user_id) REFERENCES users(id)
  9. );

评论表单

Views/articles/show.php 文件中添加评论表单:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title><?php echo $article->title; ?></title>
  5. </head>
  6. <body>
  7. <h2><?php echo $article->title; ?></h2>
  8. <p><?php echo $article->content; ?></p>
  9. <hr>
  10. <h3>评论</h3>
  11. <ul>
  12. <?php foreach ($comments as $comment): ?>
  13. <li><?php echo $comment->content; ?> - <?php echo $comment->created_at; ?></li>
  14. <?php endforeach; ?>
  15. </ul>
  16. <h4>添加评论</h4>
  17. <form action="/comments/create" method="POST">
  18. <input type="hidden" name="article_id" value="<?php echo $article->id; ?>">
  19. <label for="content">内容:</label>
  20. <textarea id="content" name="content" required></textarea><br>
  21. <input type="submit" value="发布">
  22. </form>
  23. </body>
  24. </html>

处理评论创建请求

Controllers/CommentController.php 中编写处理评论创建请求的逻辑:

  1. <?php
  2. namespace Controllers;
  3. use Models\Comment;
  4. use Helpers\Auth;
  5. class# 第九章:项目实战:创建一个博客系统 (续)
  6. 这一章继续带领你完成创建一个博客系统的实战项目,包括用户认证、文章管理和评论功能的实现。
  7. ## 文章管理 (续)
  8. ### 创建与编辑文章 (续)
  9. #### 文章模型 (续)
  10. `Models/Article.php` 中定义 `Article` 类:
  11. ```php
  12. <?php
  13. namespace Models;
  14. use PDO;
  15. class Article
  16. {
  17. public $id;
  18. public $title;
  19. public $content;
  20. public $user_id;
  21. public $created_at;
  22. public function save()
  23. {
  24. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  25. $sql = "INSERT INTO articles (title, content, user_id) VALUES (:title, :content, :user_id)";
  26. $stmt = $pdo->prepare($sql);
  27. $stmt->execute([
  28. ':title' => $this->title,
  29. ':content' => $this->content,
  30. ':user_id' => $this->user_id
  31. ]);
  32. }
  33. public static function findById($id)
  34. {
  35. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  36. $sql = "SELECT * FROM articles WHERE id = :id";
  37. $stmt = $pdo->prepare($sql);
  38. $stmt->execute([':id' => $id]);
  39. return $stmt->fetchObject(self::class);
  40. }
  41. }

编辑文章功能

文章编辑表单

Views/articles/edit.php 文件中创建文章编辑表单:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>编辑文章</title>
  5. </head>
  6. <body>
  7. <h2>编辑文章</h2>
  8. <form action="/articles/edit" method="POST">
  9. <input type="hidden" name="id" value="<?php echo $article->id; ?>">
  10. <label for="title">标题:</label>
  11. <input type="text" id="title" name="title" value="<?php echo $article->title; ?>" required><br>
  12. <label for="content">内容:</label>
  13. <textarea id="content" name="content" required><?php echo $article->content; ?></textarea><br>
  14. <input type="submit" value="更新">
  15. </form>
  16. </body>
  17. </html>

处理文章编辑请求

Controllers/ArticleController.php 中添加处理文章编辑请求的逻辑:

  1. <?php
  2. namespace Controllers;
  3. use Models\Article;
  4. class ArticleController
  5. {
  6. public function edit()
  7. {
  8. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  9. $id = $_POST['id'];
  10. $title = $_POST['title'];
  11. $content = $_POST['content'];
  12. $article = Article::findById($id);
  13. $article->title = $title;
  14. $article->content = $content;
  15. $article->save();
  16. header('Location: /articles');
  17. } else {
  18. $id = $_GET['id'];
  19. $article = Article::findById($id);
  20. require 'Views/articles/edit.php';
  21. }
  22. }
  23. }

文章的展示与分页

文章展示页面

Views/articles/index.php 文件中创建文章展示页面:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>文章列表</title>
  5. </head>
  6. <body>
  7. <h2>文章列表</h2>
  8. <a href="/articles/create">创建新文章</a>
  9. <ul>
  10. <?php foreach ($articles as $article): ?>
  11. <li>
  12. <a href="/articles/show?id=<?php echo $article->id; ?>"><?php echo $article->title; ?></a>
  13. <a href="/articles/edit?id=<?php echo $article->id; ?>">编辑</a>
  14. <a href="/articles/delete?id=<?php echo $article->id; ?>" onclick="return confirm('确定要删除吗?');">删除</a>
  15. </li>
  16. <?php endforeach; ?>
  17. </ul>
  18. </body>
  19. </html>

处理文章展示请求

Controllers/ArticleController.php 中添加处理文章展示请求的逻辑:

  1. <?php
  2. namespace Controllers;
  3. use Models\Article;
  4. class ArticleController
  5. {
  6. public function index()
  7. {
  8. $articles = Article::all();
  9. require 'Views/articles/index.php';
  10. }
  11. public function show()
  12. {
  13. $id = $_GET['id'];
  14. $article = Article::findById($id);
  15. require 'Views/articles/show.php';
  16. }
  17. public function delete()
  18. {
  19. $id = $_GET['id'];
  20. $article = Article::findById($id);
  21. $article->delete();
  22. header('Location: /articles');
  23. }
  24. }

文章分页

为了实现文章的分页展示,我们可以在 Article 类中添加分页方法:

  1. <?php
  2. namespace Models;
  3. use PDO;
  4. class Article
  5. {
  6. // ... 其他代码
  7. public static function paginate($limit, $offset)
  8. {
  9. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  10. $sql = "SELECT * FROM articles LIMIT :limit OFFSET :offset";
  11. $stmt = $pdo->prepare($sql);
  12. $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
  13. $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
  14. $stmt->execute();
  15. return $stmt->fetchAll(PDO::FETCH_CLASS, self::class);
  16. }
  17. public static function count()
  18. {
  19. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  20. $sql = "SELECT COUNT(*) as count FROM articles";
  21. $stmt = $pdo->query($sql);
  22. return $stmt->fetch(PDO::FETCH_ASSOC)['count'];
  23. }
  24. }

ArticleController.php 中修改 index 方法以支持分页:

  1. <?php
  2. namespace Controllers;
  3. use Models\Article;
  4. class ArticleController
  5. {
  6. public function index()
  7. {
  8. $page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
  9. $limit = 5;
  10. $offset = ($page - 1) * $limit;
  11. $articles = Article::paginate($limit, $offset);
  12. $total = Article::count();
  13. $pages = ceil($total / $limit);
  14. require 'Views/articles/index.php';
  15. }
  16. // ... 其他代码
  17. }

Views/articles/index.php 中添加分页导航:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>文章列表</title>
  5. </head>
  6. <body>
  7. <h2>文章列表</h2>
  8. <a href="/articles/create">创建新文章</a>
  9. <ul>
  10. <?php foreach ($articles as $article): ?>
  11. <li>
  12. <a href="/articles/show?id=<?php echo $article->id; ?>"><?php echo $article->title; ?></a>
  13. <a href="/articles/edit?id=<?php echo $article->id; ?>">编辑</a>
  14. <a href="/articles/delete?id=<?php echo $article->id; ?>" onclick="return confirm('确定要删除吗?');">删除</a>
  15. </li>
  16. <?php endforeach; ?>
  17. </ul>
  18. <div>
  19. <?php for ($i = 1; $i <= $pages; $i++): ?>
  20. <a href="/articles?page=<?php echo $i; ?>"><?php echo $i; ?></a>
  21. <?php endfor; ?>
  22. </div>
  23. </body>
  24. </html>

评论功能的实现

数据库设计

为评论创建一个数据库表。以下是 comments 表的 SQL 创建语句:

  1. CREATE TABLE comments (
  2. id INT AUTO_INCREMENT PRIMARY KEY,
  3. article_id INT NOT NULL,
  4. user_id INT NOT NULL,
  5. content TEXT NOT NULL,
  6. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  7. FOREIGN KEY (article_id) REFERENCES articles(id),
  8. FOREIGN KEY (user_id) REFERENCES users(id)
  9. );

评论表单

Views/articles/show.php 文件中添加评论表单:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title><?php echo $article->title; ?></title>
  5. </head>
  6. <body>
  7. <h2><?php echo $article->title; ?></h2>
  8. <p><?php echo $article->content; ?></p>
  9. <hr>
  10. <h3>评论</h3>
  11. <ul>
  12. <?php foreach ($comments as $comment): ?>
  13. <li><?php echo $comment->content; ?> - <?php echo $comment->created_at; ?></li>
  14. <?php endforeach; ?>
  15. </ul>
  16. <h4>添加评论</h4>
  17. <form action="/comments/create" method="POST">
  18. <input type="hidden" name="article_id" value="<?php echo $article->id; ?>">
  19. <label for="content">内容:</label>
  20. <textarea id="content" name="content" required></textarea><br>
  21. <input type="submit" value="发布">
  22. </form>
  23. </body>
  24. </html>

处理评论创建请求

Controllers/CommentController.php 中编写处理评论创建请求的逻辑:

  1. <?php
  2. namespace Controllers;
  3. use Models\Comment;
  4. use Helpers\Auth;
  5. class# 第九章:项目实战:创建一个博客系统 (续)
  6. ## 评论功能的实现 (续)
  7. ### 处理评论创建请求 (续)
  8. `Controllers/CommentController.php` 中编写处理评论创建请求的逻辑:
  9. ```php
  10. <?php
  11. namespace Controllers;
  12. use Models\Comment;
  13. use Helpers\Auth;
  14. class CommentController
  15. {
  16. public function create()
  17. {
  18. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  19. $article_id = $_POST['article_id'];
  20. $content = $_POST['content'];
  21. $user_id = Auth::user()->id;
  22. $comment = new Comment();
  23. $comment->article_id = $article_id;
  24. $comment->content = $content;
  25. $comment->user_id = $user_id;
  26. $comment->save();
  27. header('Location: /articles/show?id=' . $article_id);
  28. }
  29. }
  30. }

评论模型

Models/Comment.php 中定义 Comment 类:

  1. <?php
  2. namespace Models;
  3. use PDO;
  4. class Comment
  5. {
  6. public $id;
  7. public $article_id;
  8. public $user_id;
  9. public $content;
  10. public $created_at;
  11. public function save()
  12. {
  13. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  14. $sql = "INSERT INTO comments (article_id, user_id, content) VALUES (:article_id, :user_id, :content)";
  15. $stmt = $pdo->prepare($sql);
  16. $stmt->execute([
  17. ':article_id' => $this->article_id,
  18. ':user_id' => $this->user_id,
  19. ':content' => $this->content
  20. ]);
  21. }
  22. public static function findByArticleId($article_id)
  23. {
  24. $pdo = new PDO('mysql:host=localhost;dbname=blog', 'root', '');
  25. $sql = "SELECT * FROM comments WHERE article_id = :article_id";
  26. $stmt = $pdo->prepare($sql);
  27. $stmt->execute([':article_id' => $article_id]);
  28. return $stmt->fetchAll(PDO::FETCH_CLASS, self::class);
  29. }
  30. }

文章详情页面

Views/articles/show.php 中整合文章和评论的展示:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title><?php echo $article->title; ?></title>
  5. </head>
  6. <body>
  7. <h2><?php echo $article->title; ?></h2>
  8. <p><?php echo $article->content; ?></p>
  9. <hr>
  10. <h3>评论</h3>
  11. <ul>
  12. <?php foreach ($comments as $comment): ?>
  13. <li><?php echo $comment->content; ?> - <?php echo $comment->created_at; ?></li>
  14. <?php endforeach; ?>
  15. </ul>
  16. <h4>添加评论</h4>
  17. <form action="/comments/create" method="POST">
  18. <input type="hidden" name="article_id" value="<?php echo $article->id; ?>">
  19. <label for="content">内容:</label>
  20. <textarea id="content" name="content" required></textarea><br>
  21. <input type="submit" value="发布">
  22. </form>
  23. </body>
  24. </html>

Controllers/ArticleController.php 中修改 show 方法以包括评论数据:

  1. <?php
  2. namespace Controllers;
  3. use Models\Article;
  4. use Models\Comment;
  5. class ArticleController
  6. {
  7. public function show()
  8. {
  9. $id = $_GET['id'];
  10. $article = Article::findById($id);
  11. $comments = Comment::findByArticleId($id);
  12. require 'Views/articles/show.php';
  13. }
  14. }

项目部署

项目开发完毕后,下一步就是将其部署到生产环境。我们将介绍如何在服务器上部署项目,并使用 Docker 进行容器化部署。

部署博客系统到服务器

  1. 准备服务器环境
    • 确保服务器已安装 LAMP 或 LEMP 环境(Linux, Apache/Nginx, MySQL, PHP)
    • 配置虚拟主机,将域名指向项目的 public 目录
  2. 上传项目文件
    • 将项目文件上传到服务器上的合适位置
    • 配置文件权限,确保 config 目录不可公开访问
  3. 配置数据库
    • 在服务器上创建数据库和用户,并导入本地开发环境中的数据库
  4. 更新配置文件
    • 修改项目中的数据库配置文件(如 config/config.php),确保连接正确

使用 Docker 进行容器化部署

  1. 编写 Dockerfile
    在项目根目录下创建一个 Dockerfile 文件,内容如下:
  1. # 基础镜像
  2. FROM php:7.4-apache
  3. # 复制项目文件
  4. COPY . /var/www/html/
  5. # 安装必要的扩展
  6. RUN docker-php-ext-install pdo pdo_mysql
  7. # 设置工作目录
  8. WORKDIR /var/www/html
  1. 编写 docker-compose.yml
    在项目根目录下创建一个 docker-compose.yml 文件,内容如下:
  1. version: "3.7"
  2. services:
  3. web:
  4. build: .
  5. ports:
  6. - "80:80"
  7. volumes:
  8. - .:/var/www/html
  9. db:
  10. image: mysql:5.7
  11. environment:
  12. MYSQL_ROOT_PASSWORD: root
  13. MYSQL_DATABASE: blog
  14. MYSQL_USER: user
  15. MYSQL_PASSWORD: password
  16. ports:
  17. - "3306:3306"
  1. 启动容器
    在项目根目录下运行以下命令启动容器:
  1. docker-compose up -d

访问 http://localhost,你应该能看到博客系统的首页!