PHP 项目实战教程

wen PHP项目 1

本文目录导读:

PHP 项目实战教程

  1. 📚 PHP项目实战教程
  2. 📌 推荐学习路径
  3. 📚 资源推荐

我来为你提供一个完整的PHP项目实战教程,从基础到进阶,包含实际项目案例。

📚 PHP项目实战教程

项目准备阶段

环境搭建

# 推荐使用集成环境
- XAMPP (Windows)
- MAMP (Mac)
- LAMP (Linux)
# 或者使用Docker
docker run -p 80:80 -p 3306:3306 --name php-env -d \
  -v $(pwd)/www:/var/www/html \
  php:8.2-apache

项目结构设计

project/
├── public/          # 公共目录
│   ├── index.php   # 入口文件
│   ├── css/
│   ├── js/
│   └── images/
├── app/            # 应用目录
│   ├── controllers/
│   ├── models/
│   ├── views/
│   └── helpers/
├── config/         # 配置文件
├── database/       # 数据库文件
└── vendor/         # 第三方库

实战项目:博客管理系统

数据库设计

-- users 用户表
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    avatar VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- posts 文章表
CREATE TABLE posts (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,VARCHAR(200) NOT NULL,
    content TEXT,
    status ENUM('draft', 'published') DEFAULT 'draft',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);
-- comments 评论表
CREATE TABLE comments (
    id INT PRIMARY KEY AUTO_INCREMENT,
    post_id INT,
    user_id INT,
    content TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (post_id) REFERENCES posts(id),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

配置文件 (config/database.php)

<?php
// 数据库配置
define('DB_HOST', 'localhost');
define('DB_NAME', 'blog_db');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_CHARSET', 'utf8mb4');
// 网站配置
define('SITE_NAME', '我的博客');
define('BASE_URL', 'http://localhost/blog/');
// 会话配置
define('SESSION_LIFETIME', 3600);
// 数据库连接类
class Database {
    private static $instance = null;
    private $connection;
    private function __construct() {
        try {
            $dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
            $this->connection = new PDO($dsn, DB_USER, DB_PASS);
            $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch (PDOException $e) {
            die("数据库连接失败: " . $e->getMessage());
        }
    }
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new Database();
        }
        return self::$instance;
    }
    public function getConnection() {
        return $this->connection;
    }
}

用户认证类 (app/models/User.php)

<?php
class User {
    private $db;
    public function __construct() {
        $this->db = Database::getInstance()->getConnection();
    }
    // 用户注册
    public function register($username, $email, $password) {
        // 验证输入
        if (empty($username) || empty($email) || empty($password)) {
            return ['success' => false, 'message' => '所有字段都是必填的'];
        }
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return ['success' => false, 'message' => '邮箱格式不正确'];
        }
        // 检查用户名/邮箱是否已存在
        $stmt = $this->db->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
        $stmt->execute([$username, $email]);
        if ($stmt->rowCount() > 0) {
            return ['success' => false, 'message' => '用户名或邮箱已被使用'];
        }
        // 密码加密
        $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
        // 插入用户
        $stmt = $this->db->prepare(
            "INSERT INTO users (username, email, password) VALUES (?, ?, ?)"
        );
        try {
            $stmt->execute([$username, $email, $hashedPassword]);
            return ['success' => true, 'user_id' => $this->db->lastInsertId()];
        } catch (PDOException $e) {
            return ['success' => false, 'message' => '注册失败: ' . $e->getMessage()];
        }
    }
    // 用户登录
    public function login($username, $password) {
        $stmt = $this->db->prepare("SELECT * FROM users WHERE username = ?");
        $stmt->execute([$username]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($user && password_verify($password, $user['password'])) {
            // 登录成功,创建会话
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['username'] = $user['username'];
            $_SESSION['logged_in'] = true;
            return ['success' => true, 'user' => $user];
        }
        return ['success' => false, 'message' => '用户名或密码错误'];
    }
    // 退出登录
    public function logout() {
        session_unset();
        session_destroy();
        return true;
    }
    // 获取用户信息
    public function getUserById($userId) {
        $stmt = $this->db->prepare("SELECT id, username, email, avatar, created_at FROM users WHERE id = ?");
        $stmt->execute([$userId]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }
}

文章管理类 (app/models/Post.php)

<?php
class Post {
    private $db;
    public function __construct() {
        $this->db = Database::getInstance()->getConnection();
    }
    // 创建文章
    public function create($userId, $title, $content, $status = 'draft') {
        $stmt = $this->db->prepare(
            "INSERT INTO posts (user_id, title, content, status) VALUES (?, ?, ?, ?)"
        );
        try {
            $stmt->execute([$userId, $title, $content, $status]);
            return ['success' => true, 'post_id' => $this->db->lastInsertId()];
        } catch (PDOException $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }
    // 获取所有文章(分页)
    public function getAllPosts($page = 1, $limit = 10) {
        $offset = ($page - 1) * $limit;
        $stmt = $this->db->prepare(
            "SELECT p.*, u.username 
             FROM posts p 
             JOIN users u ON p.user_id = u.id 
             WHERE p.status = 'published'
             ORDER BY p.created_at DESC 
             LIMIT :limit OFFSET :offset"
        );
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    // 获取单篇文章
    public function getPostById($postId) {
        $stmt = $this->db->prepare(
            "SELECT p.*, u.username 
             FROM posts p 
             JOIN users u ON p.user_id = u.id 
             WHERE p.id = ?"
        );
        $stmt->execute([$postId]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }
    // 更新文章
    public function update($postId, $userId, $title, $content, $status) {
        $stmt = $this->db->prepare(
            "UPDATE posts 
             SET title = ?, content = ?, status = ? 
             WHERE id = ? AND user_id = ?"
        );
        try {
            $stmt->execute([$title, $content, $status, $postId, $userId]);
            return ['success' => true];
        } catch (PDOException $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }
    // 删除文章
    public function delete($postId, $userId) {
        // 先删除关联的评论
        $stmt = $this->db->prepare("DELETE FROM comments WHERE post_id = ?");
        $stmt->execute([$postId]);
        // 再删除文章
        $stmt = $this->db->prepare("DELETE FROM posts WHERE id = ? AND user_id = ?");
        try {
            $stmt->execute([$postId, $userId]);
            return ['success' => true];
        } catch (PDOException $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }
    // 搜索文章
    public function search($keyword, $page = 1, $limit = 10) {
        $offset = ($page - 1) * $limit;
        $searchTerm = "%{$keyword}%";
        $stmt = $this->db->prepare(
            "SELECT p.*, u.username 
             FROM posts p 
             JOIN users u ON p.user_id = u.id 
             WHERE p.status = 'published' 
             AND (p.title LIKE ? OR p.content LIKE ?)
             ORDER BY p.created_at DESC 
             LIMIT :limit OFFSET :offset"
        );
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        $stmt->execute([$searchTerm, $searchTerm]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

控制器示例 (app/controllers/PostController.php)

<?php
require_once '../models/Post.php';
require_once '../models/Comment.php';
class PostController {
    private $postModel;
    private $commentModel;
    public function __construct() {
        $this->postModel = new Post();
        $this->commentModel = new Comment();
    }
    // 显示文章列表
    public function index() {
        $page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
        $posts = $this->postModel->getAllPosts($page);
        // 加载视图
        require_once '../views/posts/index.php';
    }
    // 显示单篇文章
    public function show($id) {
        $post = $this->postModel->getPostById($id);
        $comments = $this->commentModel->getCommentsByPostId($id);
        if (!$post) {
            header('HTTP/1.0 404 Not Found');
            die('文章不存在');
        }
        require_once '../views/posts/show.php';
    }
    // 创建文章表单
    public function create() {
        // 检查登录状态
        if (!isset($_SESSION['logged_in'])) {
            header('Location: /blog/login');
            exit;
        }
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            // 处理表单提交
            $title = $_POST['title'];
            $content = $_POST['content'];
            $status = $_POST['status'] ?? 'draft';
            $result = $this->postModel->create(
                $_SESSION['user_id'],
                $title,
                $content,
                $status
            );
            if ($result['success']) {
                header('Location: /blog/posts/' . $result['post_id']);
                exit;
            } else {
                $error = $result['message'];
            }
        }
        require_once '../views/posts/create.php';
    }
    // 更新文章
    public function update($id) {
        // 获取原始文章数据
        $post = $this->postModel->getPostById($id);
        // 权限检查
        if (!$post || $post['user_id'] != $_SESSION['user_id']) {
            header('HTTP/1.0 403 Forbidden');
            die('没有权限执行此操作');
        }
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $title = $_POST['title'];
            $content = $_POST['content'];
            $status = $_POST['status'] ?? 'draft';
            $result = $this->postModel->update(
                $id,
                $_SESSION['user_id'],
                $title,
                $content,
                $status
            );
            if ($result['success']) {
                header('Location: /blog/posts/' . $id);
                exit;
            }
        }
        require_once '../views/posts/edit.php';
    }
    // 删除文章
    public function delete($id) {
        $post = $this->postModel->getPostById($id);
        // 权限检查
        if (!$post || $post['user_id'] != $_SESSION['user_id']) {
            header('HTTP/1.0 403 Forbidden');
            die('没有权限执行此操作');
        }
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $result = $this->postModel->delete($id, $_SESSION['user_id']);
            header('Location: /blog/posts');
            exit;
        }
        require_once '../views/posts/delete.php';
    }
    // 搜索文章
    public function search() {
        $keyword = $_GET['q'] ?? '';
        $page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
        $posts = $this->postModel->search($keyword, $page);
        require_once '../views/posts/search.php';
    }
}

路由配置 (public/index.php)

<?php
// 开启会话
session_start();
// 加载配置
require_once '../config/database.php';
require_once '../helpers/functions.php';
// 简单路由处理
$request = $_SERVER['REQUEST_URI'];
$path = parse_url($request, PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
// 移除BASE_URL前缀
$path = str_replace('/blog', '', $path);
$segments = explode('/', trim($path, '/'));
// 默认路由
$controller = 'Home';
$action = 'index';
$params = [];
// 解析路由
if (!empty($segments[0])) {
    $controller = ucfirst($segments[0]);
    if (!empty($segments[1])) {
        $action = $segments[1];
    }
    if (count($segments) > 2) {
        $params = array_slice($segments, 2);
    }
}
// 控制器文件路径
$controllerFile = "../app/controllers/{$controller}Controller.php";
if (file_exists($controllerFile)) {
    require_once $controllerFile;
    $controllerClass = "{$controller}Controller";
    if (class_exists($controllerClass)) {
        $controllerInstance = new $controllerClass();
        if (method_exists($controllerInstance, $action)) {
            call_user_func_array([$controllerInstance, $action], $params);
        } else {
            http_response_code(404);
            echo "页面不存在";
        }
    } else {
        http_response_code(404);
        echo "控制器类不存在";
    }
} else {
    http_response_code(404);
    echo "控制器文件不存在";
}

视图模板 (app/views/posts/index.php)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">文章列表 - <?php echo SITE_NAME; ?></title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: "Microsoft YaHei", sans-serif; background: #f5f5f5; }
        .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
        .header { background: #333; color: white; padding: 20px; }
        .nav { background: #444; padding: 10px; }
        .nav a { color: white; text-decoration: none; margin-right: 15px; }
        .post { background: white; padding: 20px; margin-bottom: 20px; border-radius: 5px; }
        .post-title { font-size: 24px; margin-bottom: 10px; color: #333; }
        .post-meta { color: #777; font-size: 14px; margin-bottom: 10px; }
        .post-content { color: #555; line-height: 1.6; }
        .footer { text-align: center; padding: 20px; color: #777; background: #333; }
    </style>
</head>
<body>
    <div class="header">
        <h1><?php echo SITE_NAME; ?></h1>
    </div>
    <div class="nav">
        <a href="/blog/">首页</a>
        <a href="/blog/posts">文章列表</a>
        <a href="/blog/search">搜索</a>
        <?php if (isset($_SESSION['logged_in'])): ?>
            <a href="/blog/posts/create">写文章</a>
            <a href="/blog/user/dashboard"><?php echo $_SESSION['username']; ?></a>
            <a href="/blog/user/logout">退出</a>
        <?php else: ?>
            <a href="/blog/user/login">登录</a>
            <a href="/blog/user/register">注册</a>
        <?php endif; ?>
    </div>
    <div class="container">
        <h2>最新文章</h2>
        <?php if (empty($posts)): ?>
            <p>暂无文章</p>
        <?php else: ?>
            <?php foreach ($posts as $post): ?>
                <div class="post">
                    <h3 class="post-title">
                        <a href="/blog/posts/show/<?php echo $post['id']; ?>">
                            <?php echo htmlspecialchars($post['title']); ?>
                        </a>
                    </h3>
                    <div class="post-meta">
                        作者:<?php echo htmlspecialchars($post['username']); ?> |
                        发布时间:<?php echo date('Y-m-d H:i', strtotime($post['created_at'])); ?>
                    </div>
                    <div class="post-content">
                        <?php echo mb_substr(strip_tags($post['content']), 0, 200); ?>...
                    </div>
                </div>
            <?php endforeach; ?>
            <!-- 分页 -->
            <div class="pagination">
                <?php if ($page > 1): ?>
                    <a href="/blog/posts?page=<?php echo $page - 1; ?>" class="btn">上一页</a>
                <?php endif; ?>
                <?php if (count($posts) == 10): ?>
                    <a href="/blog/posts?page=<?php echo $page + 1; ?>" class="btn">下一页</a>
                <?php endif; ?>
            </div>
        <?php endif; ?>
    </div>
    <div class="footer">
        <p>&copy; <?php echo date('Y'); ?> <?php echo SITE_NAME; ?></p>
    </div>
</body>
</html>

辅助函数 (app/helpers/functions.php)

<?php
// 安全输出函数
function e($string) {
    return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}
// 重定向函数
function redirect($url) {
    header("Location: " . BASE_URL . $url);
    exit;
}
// 生成CSRF令牌
function generateCSRFToken() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}
// 验证CSRF令牌
function validateCSRFToken($token) {
    return isset($_SESSION['csrf_token']) && $token === $_SESSION['csrf_token'];
}
// 检查用户是否登录
function isLoggedIn() {
    return isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;
}
// 获取当前用户ID
function getCurrentUserId() {
    return $_SESSION['user_id'] ?? null;
}
// 上传文件处理
function handleFileUpload($file, $targetDir, $allowedTypes = ['jpg', 'jpeg', 'png', 'gif']) {
    $targetDir = rtrim($targetDir, '/') . '/';
    // 创建目录
    if (!file_exists($targetDir)) {
        mkdir($targetDir, 0777, true);
    }
    // 检查文件类型
    $fileExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    if (!in_array($fileExt, $allowedTypes)) {
        return ['success' => false, 'message' => '不支持的图片类型'];
    }
    // 生成新文件名
    $newFileName = uniqid() . '.' . $fileExt;
    $targetPath = $targetDir . $newFileName;
    if (move_uploaded_file($file['tmp_name'], $targetPath)) {
        return ['success' => true, 'path' => $targetPath];
    }
    return ['success' => false, 'message' => '文件上传失败'];
}
// 分页函数
function paginate($totalItems, $perPage = 10, $currentPage = 1) {
    $totalPages = ceil($totalItems / $perPage);
    $currentPage = max(1, min($currentPage, $totalPages));
    return [
        'totalItems' => $totalItems,
        'totalPages' => $totalPages,
        'currentPage' => $currentPage,
        'perPage' => $perPage,
        'offset' => ($currentPage - 1) * $perPage
    ];
}
// 发送JSON响应
function jsonResponse($data, $statusCode = 200) {
    http_response_code($statusCode);
    header('Content-Type: application/json');
    echo json_encode($data);
    exit;
}
// 获取客户端IP
function getClientIP() {
    $ip = '';
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    } else {
        $ip = $_SERVER['REMOTE_ADDR'] ?? '';
    }
    return $ip;
}

安全最佳实践

<?php
// 1. SQL注入防护 - 使用PDO预处理
class SecureDatabase {
    private $connection;
    public function __construct() {
        $dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4";
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ];
        $this->connection = new PDO($dsn, DB_USER, DB_PASS, $options);
    }
}
// 2. XSS防护 - 输出过滤
function cleanOutput($data) {
    return htmlspecialchars($data, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// 3. CSRF防护 - 表单令牌
class CSRFProtection {
    public static function generateToken() {
        if (empty($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }
        return $_SESSION['csrf_token'];
    }
    public static function validateToken($token) {
        return hash_equals($_SESSION['csrf_token'] ?? '', $token);
    }
}
// 4. 文件上传安全
function secureFileUpload($file) {
    // 检查文件大小
    if ($file['size'] > 2 * 1024 * 1024) { // 2MB
        throw new Exception('文件太大');
    }
    // 检查MIME类型
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeType = $finfo->file($file['tmp_name']);
    $allowedMimes = [
        'image/jpeg',
        'image/png',
        'image/gif',
        'application/pdf'
    ];
    if (!in_array($mimeType, $allowedMimes)) {
        throw new Exception('文件类型不允许');
    }
    return true;
}

性能优化技巧

<?php
// 1. 缓存配置
class Cache {
    private $cacheDir;
    public function __construct($cacheDir = 'cache/') {
        $this->cacheDir = $cacheDir;
        if (!file_exists($cacheDir)) {
            mkdir($cacheDir, 0777, true);
        }
    }
    public function set($key, $data, $ttl = 3600) {
        $filename = $this->cacheDir . md5($key) . '.cache';
        $content = json_encode([
            'expires' => time() + $ttl,
            'data' => $data
        ]);
        file_put_contents($filename, $content, LOCK_EX);
    }
    public function get($key) {
        $filename = $this->cacheDir . md5($key) . '.cache';
        if (file_exists($filename)) {
            $content = json_decode(file_get_contents($filename), true);
            if ($content['expires'] > time()) {
                return $content['data'];
            }
            // 缓存过期,删除文件
            unlink($filename);
        }
        return null;
    }
}
// 2. 数据库查询优化
class QueryOptimizer {
    public function getPostsWithComments($limit = 10) {
        // 使用JOIN代替多次查询
        $sql = "
            SELECT 
                p.*,
                u.username,
                COUNT(c.id) as comment_count
            FROM posts p
            LEFT JOIN users u ON p.user_id = u.id
            LEFT JOIN comments c ON c.post_id = p.id
            GROUP BY p.id
            ORDER BY p.created_at DESC
            LIMIT ?
        ";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$limit]);
        return $stmt->fetchAll();
    }
}
// 3. Gzip压缩
if (ob_get_level() == 0) {
    ob_start('ob_gzhandler');
}
// 4. 静态文件版本控制
function asset($file) {
    $path = BASE_PATH . 'public/' . $file;
    $version = filemtime($path);
    return BASE_URL . $file . '?v=' . $version;
}

部署与运维

# 1. 环境检查脚本
#!/bin/bash
echo "检查PHP版本..."
php -v
echo "检查PHP扩展..."
php -m | grep -i "pdo\|mysqli\|openssl"
echo "检查MySQL..."
mysql --version
echo "设置权限..."
chmod -R 755 public/
chmod -R 777 uploads/ cache/
chmod 644 config/*.php
# 2. 部署脚本
#!/bin/bash
# 备份
backup_dir="backups/$(date +%Y%m%d)"
mkdir -p $backup_dir
mysqldump -u root -p blog_db > $backup_dir/database.sql
tar -czf $backup_dir/files.tar.gz public/ app/ config/
# 更新代码
git pull origin main
# 更新数据库
php migrations/run.php
# 清理缓存
rm -rf cache/*
echo "部署完成!"
# 3. 定时任务示例 (crontab)
# 每天凌晨2点备份
0 2 * * * /backup/backup.sh
# 清理过期会话
*/30 * * * * php /var/www/html/maintenance/clean_sessions.php

调试与测试

<?php
// 1. 错误日志
function logError($message, $type = 'ERROR') {
    $logPath = __DIR__ . '/../logs/';
    if (!file_exists($logPath)) {
        mkdir($logPath, 0777, true);
    }
    $filename = $logPath . date('Y-m-d') . '.log';
    $timestamp = date('Y-m-d H:i:s');
    $entry = "[$timestamp] [$type] $message" . PHP_EOL;
    file_put_contents($filename, $entry, FILE_APPEND | LOCK_EX);
}
// 2. 单元测试示例
class PostTest extends PHPUnit\Framework\TestCase {
    private $post;
    protected function setUp(): void {
        $this->post = new Post();
    }
    public function testCreatePost() {
        $result = $this->post->create(1, '测试文章', '这是测试内容');
        $this->assertTrue($result['success']);
    }
    public function testGetAllPosts() {
        $posts = $this->post->getAllPosts();
        $this->assertIsArray($posts);
        $this->assertCount(10, $posts);
    }
}
// 3. Xdebug配置 (php.ini)
/*
[xdebug]
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=127.0.0.1
xdebug.client_port=9000
xdebug.log=/var/log/xdebug.log
*/

通过这个实战教程,你学到了:

  1. MVC架构模式:如何组织代码结构
  2. 数据库操作:PDO安全连接和查询
  3. 用户认证系统:注册、登录、权限控制
  4. CRUD操作:文章的创建、读取、更新、删除
  5. 安全防护:SQL注入、XSS、CSRF防护
  6. 性能优化:缓存、查询优化、Gzip压缩
  7. 部署运维:备份、更新、定时任务

📌 推荐学习路径

  1. 基础阶段:HTML/CSS → PHP基础 → MySQL
  2. 进阶阶段:OOP → 模式设计 → 安全编程
  3. 实战阶段:独立完成小型项目 → 参与开源
  4. 框架学习:Laravel / ThinkPHP / Symfony

📚 资源推荐

  • 官方文档: php.net
  • 在线教程: W3Schools, PHP The Right Way
  • 框架指南: Laravel Official Docs
  • 代码规范: PSR-1, PSR-4, PSR-12

实践是最好的学习方式!尝试自己扩展这个项目,添加更多功能,比如标签系统、好友关注、消息通知等,祝你学习顺利!

抱歉,评论功能暂时关闭!