PHP 怎么PHP Boilerplate

wen PHP项目 2

本文目录导读:

PHP 怎么PHP Boilerplate

  1. 什么是 PHP Boilerplate
  2. 基本项目结构
  3. 基础 Boilerplate 示例
  4. 配置文件 (config.php)
  5. .htaccess 配置
  6. 模型基类 (Model.php)
  7. 使用 Composer 的 Boilerplate
  8. 模板引擎集成 (可选)
  9. 安全加固
  10. 快速开始脚本
  11. 最佳实践

我来详细介绍 PHP Boilerplate(样板代码/脚手架)的创建和使用方法。

什么是 PHP Boilerplate

Boilerplate 是预先写好的代码模板,可以帮助你快速启动新项目,避免重复编写基础代码。

基本项目结构

project/
├── public/           # 公开访问目录
│   ├── index.php    # 入口文件
│   ├── css/         # 样式文件
│   └── js/          # JavaScript文件
├── src/             # 源代码
│   ├── Core/        # 核心类
│   ├── Models/      # 数据模型
│   ├── Views/       # 视图文件
│   └── Controllers/ # 控制器
├── config/          # 配置文件
├── vendor/          # 依赖包
└── .htaccess        # Apache配置

基础 Boilerplate 示例

1 入口文件 (index.php)

<?php
declare(strict_types=1);
/**
 * PHP Boilerplate 入口文件
 */
// 1. 定义常量
define('ROOT_PATH', __DIR__ . '/../');
define('APP_PATH', ROOT_PATH . 'src/');
define('VIEW_PATH', APP_PATH . 'Views/');
// 2. 加载配置
require_once ROOT_PATH . 'config/config.php';
// 3. 自动加载类
spl_autoload_register(function ($className) {
    $prefix = 'App\\';
    $baseDir = APP_PATH;
    // 检查类名前缀
    $len = strlen($prefix);
    if (strncmp($prefix, $className, $len) !== 0) {
        return;
    }
    // 获取相对类名并转换路径
    $relativeClass = substr($className, $len);
    $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
    // 加载文件
    if (file_exists($file)) {
        require $file;
    }
});
// 4. 错误处理
error_reporting(E_ALL);
ini_set('display_errors', '1');
// 5. 启动应用
use App\Core\Router;
use App\Core\Request;
use App\Core\Response;
$router = new Router();
$request = new Request();
$response = new Response();
// 定义路由
$router->get('/', 'HomeController@index');
$router->get('/users', 'UserController@index');
$router->post('/users', 'UserController@create');
// 运行应用
try {
    $router->dispatch($request, $response);
} catch (Exception $e) {
    $response->setStatusCode(500);
    echo "Error: " . $e->getMessage();
}

2 路由类 (Router.php)

<?php
namespace App\Core;
/**
 * 简单路由器
 */
class Router
{
    private array $routes = [
        'GET' => [],
        'POST' => [],
        'PUT' => [],
        'DELETE' => []
    ];
    public function get(string $path, string $handler): void
    {
        $this->addRoute('GET', $path, $handler);
    }
    public function post(string $path, string $handler): void
    {
        $this->addRoute('POST', $path, $handler);
    }
    public function put(string $path, string $handler): void
    {
        $this->addRoute('PUT', $path, $handler);
    }
    public function delete(string $path, string $handler): void
    {
        $this->addRoute('DELETE', $path, $handler);
    }
    private function addRoute(string $method, string $path, string $handler): void
    {
        $this->routes[$method][$path] = $handler;
    }
    public function dispatch(Request $request, Response $response): void
    {
        $method = $request->getMethod();
        $uri = $request->getUri();
        // 查找路由
        if (isset($this->routes[$method][$uri])) {
            $handler = $this->routes[$method][$uri];
            [$controller, $action] = explode('@', $handler);
            $controllerClass = 'App\\Controllers\\' . $controller;
            if (class_exists($controllerClass)) {
                $controllerObj = new $controllerClass();
                if (method_exists($controllerObj, $action)) {
                    $controllerObj->{$action}($request, $response);
                    return;
                }
            }
        }
        // 404处理
        $response->setStatusCode(404);
        $response->setContent('Page not found');
        $response->send();
    }
}

3 请求和响应类

<?php
namespace App\Core;
/**
 * 请求处理类
 */
class Request
{
    public function getMethod(): string
    {
        return $_SERVER['REQUEST_METHOD'];
    }
    public function getUri(): string
    {
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        return rtrim($uri, '/') ?: '/';
    }
    public function getPost(string $key = null)
    {
        if ($key === null) {
            return $_POST;
        }
        return $_POST[$key] ?? null;
    }
    public function getQuery(string $key = null)
    {
        if ($key === null) {
            return $_GET;
        }
        return $_GET[$key] ?? null;
    }
    public function getJson()
    {
        return json_decode(file_get_contents('php://input'), true) ?? [];
    }
}
/**
 * 响应处理类
 */
class Response
{
    private int $statusCode = 200;
    private string $content = '';
    private array $headers = [];
    public function setStatusCode(int $code): void
    {
        $this->statusCode = $code;
    }
    public function setContent(string $content): void
    {
        $this->content = $content;
    }
    public function setHeader(string $name, string $value): void
    {
        $this->headers[$name] = $value;
    }
    public function json(mixed $data): void
    {
        $this->setHeader('Content-Type', 'application/json');
        $this->content = json_encode($data);
    }
    public function send(): void
    {
        http_response_code($this->statusCode);
        foreach ($this->headers as $name => $value) {
            header("$name: $value");
        }
        echo $this->content;
    }
}

配置文件 (config.php)

<?php
/**
 * 配置文件
 */
return [
    'app' => [
        'name' => 'My PHP Application',
        'env' => 'development', // development, production
        'debug' => true,
        'url' => 'http://localhost',
    ],
    'database' => [
        'host' => 'localhost',
        'port' => 3306,
        'database' => 'myapp',
        'username' => 'root',
        'password' => '',
        'charset' => 'utf8mb4',
    ],
    'security' => [
        'secret_key' => 'your-secret-key-here',
        'token_expire' => 3600, // 1 hour
    ],
    'paths' => [
        'logs' => ROOT_PATH . 'logs/',
        'uploads' => ROOT_PATH . 'public/uploads/',
    ]
];

.htaccess 配置

Options -Indexes
DirectoryIndex index.php
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    # Force HTTPS
    # RewriteCond %{HTTPS} !=on
    # RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
    # Remove trailing slashes
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)/$ /$1 [L,R=301]
    # Route all requests to index.php
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>
# Security headers
<IfModule mod_headers.c>
    Header set X-Content-Type-Options "nosniff"
    Header set X-Frame-Options "SAMEORIGIN"
    Header set X-XSS-Protection "1; mode=block"
</IfModule>
# Disable directory browsing
Options -Indexes
# Block access to sensitive files
<FilesMatch "(\.(htaccess|htpasswd|ini|log|sh|inc|bak)$|config\.json|composer\.json)">
    Order allow,deny
    Deny from all
</FilesMatch>

模型基类 (Model.php)

<?php
namespace App\Core;
use PDO;
use PDOException;
/**
 * 数据库模型基类
 */
abstract class Model
{
    protected PDO $connection;
    protected string $table;
    protected string $primaryKey = 'id';
    public function __construct()
    {
        $this->connect();
    }
    private function connect(): void
    {
        $config = require ROOT_PATH . 'config/config.php';
        $dbConfig = $config['database'];
        try {
            $dsn = "mysql:host={$dbConfig['host']};dbname={$dbConfig['database']};charset={$dbConfig['charset']}";
            $this->connection = new PDO(
                $dsn,
                $dbConfig['username'],
                $dbConfig['password'],
                [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::ATTR_EMULATE_PREPARES => false,
                ]
            );
        } catch (PDOException $e) {
            throw new PDOException("Database connection failed: " . $e->getMessage());
        }
    }
    public function findAll(): array
    {
        $stmt = $this->connection->prepare("SELECT * FROM {$this->table}");
        $stmt->execute();
        return $stmt->fetchAll();
    }
    public function findById(int $id)
    {
        $stmt = $this->connection->prepare("SELECT * FROM {$this->table} WHERE {$this->primaryKey} = ?");
        $stmt->execute([$id]);
        return $stmt->fetch();
    }
    public function create(array $data): int
    {
        $columns = implode(', ', array_keys($data));
        $placeholders = ':' . implode(', :', array_keys($data));
        $sql = "INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})";
        $stmt = $this->connection->prepare($sql);
        foreach ($data as $key => $value) {
            $stmt->bindValue(":$key", $value);
        }
        $stmt->execute();
        return (int)$this->connection->lastInsertId();
    }
    public function update(int $id, array $data): bool
    {
        $fields = [];
        foreach ($data as $key => $value) {
            $fields[] = "$key = :$key";
        }
        $sql = "UPDATE {$this->table} SET " . implode(', ', $fields) . " WHERE {$this->primaryKey} = :id";
        $stmt = $this->connection->prepare($sql);
        $stmt->bindValue(':id', $id);
        foreach ($data as $key => $value) {
            $stmt->bindValue(":$key", $value);
        }
        return $stmt->execute();
    }
    public function delete(int $id): bool
    {
        $stmt = $this->connection->prepare("DELETE FROM {$this->table} WHERE {$this->primaryKey} = ?");
        return $stmt->execute([$id]);
    }
}

使用 Composer 的 Boilerplate

如果你想使用现成的 Boilerplate,推荐以下方案:

1 使用 Laravel

composer create-project --prefer-dist laravel/laravel my-app

2 使用 Symfony Skeleton

composer create-project symfony/skeleton my-app

3 使用 Slim Framework

composer create-project slim/slim-skeleton my-app

模板引擎集成 (可选)

<?php
// 使用 Twig 模板引擎
require_once 'vendor/autoload.php';
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
$loader = new FilesystemLoader(VIEW_PATH);
$twig = new Environment($loader, [
    'cache' => ROOT_PATH . 'cache/twig/',
    'debug' => true,
]);
// 在你的控制器中使用
$twig->render('home/index.html', ['name' => 'World']);

安全加固

<?php
// SecurityHelper.php
namespace App\Helpers;
class Security
{
    public static function sanitize(string $data): string
    {
        $data = trim($data);
        $data = stripslashes($data);
        $data = htmlspecialchars($data);
        return $data;
    }
    public static function validateEmail(string $email): bool
    {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }
    public static function generateCsrfToken(): string
    {
        if (empty($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }
        return $_SESSION['csrf_token'];
    }
    public static function validateCsrfToken(): bool
    {
        $token = $_POST['csrf_token'] ?? '';
        return isset($_SESSION['csrf_token']) && 
               hash_equals($_SESSION['csrf_token'], $token);
    }
}

快速开始脚本

创建 create_project.php

<?php
/**
 * 快速创建项目结构的脚本
 */
$projectName = readline("Project name: ");
$projectPath = getcwd() . '/' . $projectName;
// 创建目录结构
$dirs = [
    'public/css',
    'public/js',
    'public/uploads',
    'src/Core',
    'src/Models',
    'src/Views',
    'src/Controllers',
    'src/Helpers',
    'config',
    'logs',
    'cache',
];
foreach ($dirs as $dir) {
    if (!file_exists("$projectPath/$dir")) {
        mkdir("$projectPath/$dir", 0777, true);
    }
}
echo "Project $projectName created successfully!\n";

最佳实践

  1. 保持简单:不要过度设计,从小开始

  2. 使用命名空间:组织代码结构

  3. 遵循 PSR 标准:PSR-4自动加载,PSR-12编码规范

  4. 分离关注点:MVC模式

  5. 配置管理:环境配置文件分离

  6. 错误处理:开发和生产环境不同的错误处理

  7. 安全考虑:SQL注入、XSS攻击防护

  8. 性能优化:使用OPcache缓存,合理使用数据库

这个基础 Boilerplate 可以帮助你快速开始新项目,根据实际需求可以选择使用现有的框架(如 Laravel)或在此基础上扩展,记住要从简单开始,逐步添加功能。

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