关于 php-development
php-development 是面向 PHP 开发者的指导技能,覆盖 PHP 8.x、PDO 数据访问和 REST API 构建。它提供安全实践、验证流程及 XAMPP 配置参考,并可用于多个编码客户端。
主要功能
- 01PHP 8.x 开发指导
- 02使用 PDO 与预处理语句
- 03REST API 与身份验证设计
- 04输入验证及 XSS、CSRF 防护
- 05XAMPP 环境配置指导
适用场景
- 01构建 PHP REST API
- 02通过 PDO 实现安全数据库操作
- 03配置 XAMPP 开发环境
- 04检查 PHP 更改并验证边界情况
MCP 相关
此技能不要求 MCP 服务;README 提供不使用 MCP 的本地指导与验证流程。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ practicalswan/ agent-skills --skill php-development安装后在 Claude Code 中输入/php-development确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CHANGELOG.mdLICENSE.txtexamples/pdo-database-patterns.phpreferences/php-8-4-api-patterns-2026.mdscripts/xampp-setup.ps1详细介绍
PHP 开发
针对当前 PHP 8.x 版本、PHPUnit 11+、Composer 2.x,以及基于 PDO 的 MySQL 或 MariaDB 应用进行了优化。
遵循 https://php.net 上的 PHP 官方文档,提供构建高质量 PHP 应用的专家指导,涵盖 PHP 8.0+、用于安全数据库访问的 PDO、RESTful API 设计以及 XAMPP 环境配置。
- 在可用时,利用原生并行子 Agent 调度和 200k+ 上下文窗口。
跨客户端可移植性
此技能旨在兼容 GitHub Copilot、Claude Code 和 Codex。
- GitHub Copilot:将文件夹放在 Copilot 可见的技能路径中;如果无法发现文件夹,则将工作流封装在项目说明中。
- Claude Code:将文件夹放在本地技能目录或兼容的插件源中。
- Codex:将文件夹安装或同步到
$CODEX_HOME/skills/php-development,并在进行重大更改后重启 Codex。
MCP 可用性与回退方案
首选 MCP 服务:无需
- 回退提示词:“不使用 MCP,使用 PHP Development 技能。依靠其本地说明、内置资源、标准 Shell 或编辑器工具,以及直接验证。得出结论前,展示所依据的证据。”
- 如果当前宿主未提供 MCP 操作,不要声称使用了该操作。
- 将本地文件、测试、渲染输出、日志或屏幕截图作为回退证据。
反模式
- 直接插入 SQL:在 PHP 数据访问中,预处理语句是正确性和安全性的基准。
- 混合请求解析、业务规则和渲染:紧密耦合的脚本更难测试,也难以演进为 API。
- 认为仅靠验证就能防止 XSS:将用户控制的内容渲染回 HTML 时,输出编码仍然重要。
验证协议
在声称“技能已成功应用”之前:
- 通过/未通过:PHP Development 实现需说明目标运行时、框架版本和受影响的文件。
- 通过/未通过:针对已更改部分运行构建、Lint、测试或同等的本地验证。
- 通过/未通过:处理错误、依赖漂移和环境差异等边界情况,或明确说明这些情况不在范围内。
- 压力测试场景:将工作流应用于一项能通过正常路径测试、但会在某个边界条件下失败的更改。
- 成功指标:零未经测试的成功声明;每项实现声明都必须对应一条命令或一个产物。
前后示例
<?php
// Before
$stmt = $pdo->query("SELECT * FROM users WHERE email = '$email'");
$user = $stmt->fetch();
// After
$stmt = $pdo->prepare('SELECT id, email, password_hash FROM users WHERE email = :email LIMIT 1');
$stmt->execute(['email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
将字符串插值替换为预处理语句,并采用更精简的结果结构。
激活条件
使用“症状 -> 操作”触发条件:满足任一条件时,应用此技能并按下方协议进行验证。
PHP 开发核心:
- 使用恰当的 HTTP 方法构建 PHP RESTful API
- 使用 XAMPP(Apache + MySQL + PHP)环境
- 使用 PDO 实现安全的数据库操作
- 创建身份验证和会话管理系统
- 处理文件上传和表单提交
数据库与数据层:
- 使用 PDO 将 PHP 连接到 MySQL/MariaDB
- 编写预处理语句以防止 SQL 注入
- 实现事务处理以确保数据完整性
- 创建用于数据访问的仓储模式
- 比较 MySQLi 与 PDO
安全与最佳实践:
- 实现密码哈希(password_hash、password_verify)
- 防范 XSS、CSRF 和 SQL 注入
- 验证并清理用户输入
- 管理会话和身份验证令牌
- 为 API 访问配置 CORS 标头
API 开发:
- 设计使用恰当 HTTP 状态码的 RESTful 端点
- 处理 JSON 请求和响应
- 实现用于身份验证和授权的中间件
- 错误处理和日志记录
- 速率限制和 API 版本管理
第 1 部分:PHP 8.0+ 基础
现代 PHP 特性
<?php
// Named arguments (PHP 8.0+)
function createUser(string $name, string $email, bool $isAdmin = false): User {
return new User($name, $email, $isAdmin);
}
// Call with named arguments
$user = createUser(email: '[email protected]', name: 'John Doe');
// Union types (PHP 8.0+)
function processValue(string|int|float $value): string {
return (string)$value;
}
// Nullsafe operator (PHP 8.0+)
$country = $session?->user?->address?->country ?? 'Unknown';
// Constructor property promotion (PHP 8.0+)
class User {
public function __construct(
public string $name,
public string $email,
private string $passwordHash
) {}
}
类型声明与严格类型
<?php
declare(strict_types=1); // Enforce type safety
// Typed properties and return types
class Recipe {
private int $id;
private string $title;
private ?DateTime $createdAt;
public function __construct(int $id, string $title) {
$this->id = $id;
$this->title = $title;
}
public function getTitle(): string {
return $this->title;
}
public function setCreatedAt(?DateTime $date): void {
$this->createdAt = $date;
}
}
// Union and intersection types
function processData(string|array $data): string|int {
return is_array($data) ? count($data) : strlen($data);
}
第 2 部分:PDO 数据库集成
数据库连接类
<?php
class Database {
private static ?PDO $instance = null;
public static function getInstance(): PDO {
if (self::$instance === null) {
$host = $_ENV['DB_HOST'] ?? 'localhost';
$dbname = $_ENV['DB_NAME'] ?? 'recipe_sharing_system';
$username = $_ENV['DB_USER'] ?? 'root';
$password = $_ENV['DB_PASSWORD'] ?? '';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
try {
self::$instance = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
} catch (PDOException $e) {
error_log("Database connection failed: " . $e->getMessage());
throw new RuntimeException("Database connection error");
}
}
return self::$instance;
}
}
安全的预处理语句
<?php
class UserRepository {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
// Find user by email with prepared statement
public function findByEmail(string $email): ?array {
$stmt = $this->db->prepare(
"SELECT id, email, password_hash, role, status
FROM user
WHERE email = :email LIMIT 1"
);
$stmt->bindParam(':email', $email, PDO::PARAM_STR);
$stmt->execute();
$user = $stmt->fetch();
return $user ?: null;
}
// Create new user with password hashing
public function create(string $name, string $email, string $password): int {
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $this->db->prepare(
"INSERT INTO user (name, email, password_hash, role, status, created_at, updated_at)
VALUES (:name, :email, :password_hash, 'user', 'active', NOW(), NOW())"
);
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
$stmt->bindParam(':email', $email, PDO::PARAM_STR);
$stmt->bindParam(':password_hash', $passwordHash, PDO::PARAM_STR);
$stmt->execute();
return (int) $this->db->lastInsertId();
}
// Authentication with password verification
public function authenticate(string $email, string $password): ?array {
$user = $this->findByEmail($email);
if ($user === null) {
return null;
}
if (!password_verify($password, $user['password_hash'])) {
return null;
}
// Check if password needs rehash
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
$newHash = password_hash($password, PASSWORD_DEFAULT);
$this->updatePasswordHash($user['id'], $newHash);
}
unset($user['password_hash']); // Remove sensitive data
return $user;
}
private function updatePasswordHash(int $userId, string $hash): void {
$stmt = $this->db->prepare(
"UPDATE user SET password_hash = :hash WHERE id = :id"
);
$stmt->execute([':hash' => $hash, ':id' => $userId]);
}
}
事务管理
<?php
class RecipeService {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
// Create recipe with ingredients, instructions, and images in a transaction
public function createRecipeWithDetails(array $recipeData, array $ingredients, array $instructions): int {
try {
$this->db->beginTransaction();
// Insert recipe
$stmt = $this->db->prepare(
"INSERT INTO recipe (title, description, category, difficulty, prep_time, cook_time, servings, author_id, status, created_at, updated_at)
VALUES (:title, :description, :category, :difficulty, :prep_time, :cook_time, :servings, :author_id, 'pending', NOW(), NOW())"
);
$stmt->execute([
':title' => $recipeData['title'],
':description' => $recipeData['description'],
':category' => $recipeData['category'],
':difficulty' => $recipeData['difficulty'],
':prep_time' => $recipeData['prepTime'],
':cook_time' => $recipeData['cookTime'],
':servings' => $recipeData['servings'],
':author_id' => $recipeData['authorId'],
]);
$recipeId = (int) $this->db->lastInsertId();
// Insert ingredients
$ingredientStmt = $this->db->prepare(
"INSERT INTO ingredient (recipe_id, name, quantity, unit, sort_order, created_at, updated_at)
VALUES (:recipe_id, :name, :quantity, :unit, :sort_order, NOW(), NOW())"
);
foreach ($ingredients as $index => $ingredient) {
$ingredientStmt->execute([
':recipe_id' => $recipeId,
':name' => $ingredient['name'],
':quantity' => $ingredient['quantity'],
':unit' => $ingredient['unit'],
':sort_order' => $index,
]);
}
// Insert instructions
$instructionStmt = $this->db->prepare(
"INSERT INTO instruction (recipe_id, step_number, instruction_text, created_at, updated_at)
VALUES (:recipe_id, :step_number, :instruction_text, NOW(), NOW())"
);
foreach ($instructions as $index => $instruction) {
$instructionStmt->execute([
':recipe_id' => $recipeId,
':step_number' => $index + 1,
':instruction_text' => $instruction['text'],
]);
}
$this->db->commit();
return $recipeId;
} catch (Exception $e) {
$this->db->rollBack();
error_log("Failed to create recipe: " . $e->getMessage());
throw $e;
}
}
}
第 3 部分:RESTful API 开发
JSON 响应辅助函数
<?php
class Response {
public static function json(mixed $data, int $statusCode = 200): never {
http_response_code($statusCode);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
exit;
}
public static function error(string $message, int $statusCode = 400): never {
self::json([
'success' => false,
'error' => $message,
], $statusCode);
}
public static function success(mixed $data = null, string $message = 'Success'): never {
self::json([
'success' => true,
'message' => $message,
'data' => $data,
]);
}
}
CORS 中间件
<?php
// Handle CORS headers
$allowedOrigins = [
'http://localhost:5173', // Vite dev server
'http://localhost:3000', // Alternative dev server
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins, true)) {
header("Access-Control-Allow-Origin: $origin");
}
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Credentials: true');
// Handle preflight requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
身份验证中间件
<?php
function requireAuth(): array {
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
Response::error('Unauthorized: Missing or invalid token', 401);
}
$token = $matches[1];
// Validate token (example withJWT)
try {
$payload = JWT::decode($token, $_ENV['JWT_SECRET'], ['HS256']);
return (array) $payload;
} catch (Exception $e) {
Response::error('Unauthorized: Invalid token', 401);
}
}
function requireAdmin(): array {
$user = requireAuth();
if ($user['role'] !== 'admin') {
Response::error('Forbidden: Admin access required', 403);
}
return $user;
}
API 控制器示例
<?php
require_once '../../config/database.php';
require_once '../../middleware/cors.php';
require_once '../../utils/response.php';
class RecipeController {
private PDO $db;
public function __construct() {
$this->db = Database::getInstance();
}
// GET /api/recipes - Get all published recipes
public function index(): void {
$category = $_GET['category'] ?? null;
$difficulty = $_GET['difficulty'] ?? null;
$search = $_GET['search'] ?? null;
$limit = (int)($_GET['limit'] ?? 20);
$offset = (int)($_GET['offset'] ?? 0);
$query = "SELECT r.*, u.name as author_name,
COUNT(DISTINCT rv.id) as view_count,
COUNT(DISTINCT lr.id) as like_count,
AVG(rev.rating) as average_rating
FROM recipe r
JOIN user u ON r.author_id = u.id
LEFT JOIN recipe_view rv ON r.id = rv.recipe_id
LEFT JOIN like_record lr ON r.id = lr.recipe_id
LEFT JOIN review rev ON r.id = rev.recipe_id
WHERE r.status = 'published'";
$params = [];
if ($category !== null) {
$query .= " AND r.category = :category";
$params[':category'] = $category;
}
if ($difficulty !== null) {
$query .= " AND r.difficulty = :difficulty";
$params[':difficulty'] = $difficulty;
}
if ($search !== null) {
$query .= " AND (r.title LIKE :search OR r.description LIKE :search)";
$searchTerm = "%$search%";
$params[':search'] = $searchTerm;
$params[':search2'] = $searchTerm;
}
$query .= " GROUP BY r.id ORDER BY r.created_at DESC LIMIT :limit OFFSET :offset";
$stmt = $this->db->prepare($query);
$stmt->execute($params);
$recipes = $stmt->fetchAll();
Response::success($recipes);
}
// GET /api/recipes/:id - Get recipe by ID
public function show(int $id): void {
$stmt = $this->db->prepare(
"SELECT r.*, u.name as author_name, u.email as author_email,
GROUP_CONCAT(CONCAT(i.name, ' (', i.quantity, ' ', i.unit, ')') SEPARATOR ', ') as ingredients
FROM recipe r
JOIN user u ON r.author_id = u.id
LEFT JOIN ingredient i ON r.id = i.recipe_id
WHERE r.id = :id
GROUP BY r.id"
);
$stmt->execute([':id' => $id]);
$recipe = $stmt->fetch();
if ($recipe === false) {
Response::error('Recipe not found', 404);
}
// Fetch instructions
$instStmt = $this->db->prepare(
"SELECT step_number, instruction_text
FROM instruction
WHERE recipe_id = :recipe_id
ORDER BY step_number"
);
$instStmt->execute([':recipe_id' => $id]);
$recipe['instructions'] = $instStmt->fetchAll();
Response::success($recipe);
}
// POST /api/recipes - Create new recipe
public function store(): void {
$user = requireAuth();
$data = json_decode(file_get_contents('php://input'), true);
// Validate required fields
if (empty($data['title']) || empty($data['description'])) {
Response::error('Title and description are required');
}
$recipeData = [
'title' => $data['title'],
'description' => $data['description'],
'category' => $data['category'] ?? 'Uncategorized',
'difficulty' => $data['difficulty'] ?? 'Medium',
'prepTime' => (int)($data['prepTime'] ?? 0),
'cookTime' => (int)($data['cookTime'] ?? 0),
'servings' => (int)($data['servings'] ?? 1),
'authorId' => $user['id'],
];
$recipeService = new RecipeService($this->db);
try {
$recipeId = $recipeService->createRecipeWithDetails(
$recipeData,
$data['ingredients'] ?? [],
$data['instructions'] ?? []
);
Response::success(['id' => $recipeId], 'Recipe created successfully', 201);
} catch (Exception $e) {
Response::error('Failed to create recipe: ' . $e->getMessage(), 500);
}
}
}
第 4 部分:输入验证与清理
验证函数
<?php
class Validator {
public static function email(string $email): bool {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
public static function string(string $value, int $min = 1, int $max = 255): bool {
$length = strlen($value);
return $length >= $min && $length <= $max;
}
public static function integer(int $value, int $min = PHP_INT_MIN, int $max = PHP_INT_MAX): bool {
return $value >= $min && $value <= $max;
}
public static function enum(string $value, array $allowed): bool {
return in_array($value, $allowed, true);
}
public static function required(array $data, array $fields): array {
$errors = [];
foreach ($fields as $field) {
if (empty($data[$field])) {
$errors[] = "$field is required";
}
}
return $errors;
}
public static function sanitize(string $input): string {
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
}
验证示例
<?php
function validateRecipeData(array $data): array {
$errors = [];
// Validate title
if (empty($data['title'])) {
$errors[] = 'Title is required';
} elseif (!Validator::string($data['title'], 3, 200)) {
$errors[] = 'Title must be between 3 and 200 characters';
}
// Validate email
if (!empty($data['email']) && !Validator::email($data['email'])) {
$errors[] = 'Invalid email address';
}
// Validate rating
if (isset($data['rating']) && !Validator::integer((int)$data['rating'], 1, 5)) {
$errors[] = 'Rating must be between 1 and 5';
}
// Validate difficulty
if (!empty($data['difficulty']) &&
!Validator::enum($data['difficulty'], ['Easy', 'Medium', 'Hard'])) {
$errors[] = 'Difficulty must be Easy, Medium, or Hard';
}
// Sanitize all string inputs
foreach ($data as $key => $value) {
if (is_string($value)) {
$data[$key] = Validator::sanitize($value);
}
}
return ['errors' => $errors, 'data' => $data];
}
第 5 部分:安全最佳实践
密码管理
<?php
class PasswordManager {
public static function hash(string $password): string {
// Use algorithm recommended by PHP
return password_hash($password, PASSWORD_DEFAULT);
}
public static function verify(string $password, string $hash): bool {
return password_verify($password, $hash);
}
public static function needsRehash(string $hash): bool {
return password_needs_rehash($hash, PASSWORD_DEFAULT);
}
public static function validateStrength(string $password): array {
$errors = [];
if (strlen($password) < 8) {
$errors[] = 'Password must be at least 8 characters';
}
if (!preg_match('/[A-Z]/', $password)) {
$errors[] = 'Password must contain at least one uppercase letter';
}
if (!preg_match('/[a-z]/', $password)) {
$errors[] = 'Password must contain at least one lowercase letter';
}
if (!preg_match('/[0-9]/', $password)) {
$errors[] = 'Password must contain at least one number';
}
if (!preg_match('/[!@#$%^&*(),.?":{}|<>]/', $password)) {
$errors[] = 'Password must contain at least one special character';
}
return $errors;
}
}
会话管理
<?php
class Session {
public static function start(): void {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
public static function set(string $key, mixed $value): void {
$_SESSION[$key] = $value;
}
public static function get(string $key, mixed $default = null): mixed {
return $_SESSION[$key] ?? $default;
}
public static function remove(string $key): void {
unset($_SESSION[$key]);
}
public static function destroy(): void {
$_SESSION = [];
session_destroy();
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
}
public static function regenerateId(): void {
session_regenerate_id(true);
}
}
CSRF 防护
<?php
class CsrfProtection {
public static function generateToken(): string {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
public static function validateToken(string $token): bool {
return isset($_SESSION['csrf_token']) &&
hash_equals($_SESSION['csrf_token'], $token);
}
public static function invalidateToken(): void {
unset($_SESSION['csrf_token']);
}
public static function getInputField(): string {
$token = self::generateToken();
return "<input type='hidden' name='csrf_token' value='$token'>";
}
}
第 6 部分:XAMPP 配置
用于 URL 重写的 .htaccess
RewriteEngine On
# Redirect trailing slashes
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Handle API routes
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^api/(.*)$ api/index.php [QSA,L]
# Handle frontend routes (SPA)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.html [QSA,L]
PHP 配置(php.ini)
; Enable error reporting for development
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
; Log errors in production
log_errors = On
error_log = "C:/xampp/php/logs/php_error.log"
; Increase upload limits
upload_max_filesize = 10M
post_max_size = 10M
; Enable PDO extensions
extension=pdo_mysql
extension=mysqli
; Enable session handling
session.save_handler = files
session.save_path = "C:/xampp/tmp"
session.use_strict_mode = 1
session.cookie_httponly = 1
session.cookie_secure = 0 ; Set to 1 if HTTPS
session.use_only_cookies = 1
; Set timezone
date.timezone = "Asia/Bangkok"
PHP 开发最佳实践
代码风格(PSR-12)
- 使用严格类型(
declare(strict_types=1)) - 遵循 PSR-12 编码规范
- 为所有函数和方法添加类型提示
- 使用命名空间实现类的自动加载
- 使用 try-catch 块处理异常
安全
- 始终使用 PDO 预处理语句
- 使用
password_hash()对密码进行哈希处理 - 验证所有用户输入
- 对输出进行清理,以防止 XSS
- 在生产环境中使用 HTTPS
- 实施 CSRF 防护
API 设计
- 使用适当的 HTTP 状态码(200、201、400、401、403、404、500)
- 返回 JSON 响应
- 处理 CORS 标头
- 实施身份验证中间件
- 对端点实施速率限制
数据库
- 使用 PDO 连接数据库
- 为多步骤操作实施事务
- 在预处理语句中使用命名参数
- 妥善处理连接错误
- 正确关闭连接
常见陷阱
- 直接插入 SQL:预处理语句是 PHP 数据访问正确性和安全性的基本要求。
- 混合处理请求解析、业务规则和渲染:紧密耦合的脚本难以测试,也难以迁移为 API。
- 忽视输出编码:仅验证输入并不能防止将数据重新渲染给用户时出现 XSS。
参考资料与资源
文档
- PHP 8.4+ API 模式 — 现代 PHP API 开发模式
示例
- PDO 数据库模式 — PHP PDO 数据库集成示例
脚本
- XAMPP 设置脚本 — 用于配置 XAMPP 以进行 PHP 开发的 PowerShell 脚本
官方文档
- PHP 手册 — 完整的 PHP 参考资料
- PDO for MySQL — PDO MySQL 驱动程序文档
- 密码哈希 — 安全的密码函数
- REST API 最佳实践 — API 设计原则
PHP 标准
- PSR-12:扩展编码风格 — 现代 PHP 编码风格
- XAMPP 文档 — XAMPP 设置与配置
安全资源
- OWASP PHP 安全 — XSS 防护
- SQL 注入防护 — SQL 注入防护
- PHP 安全指南 — PHP 官方安全注意事项
相关技能
- sql-development:当工作流还需要 SQL 查询、架构和性能调优工作时使用。
- code-quality:当工作流还需要两阶段审查(先检查规范符合性,再检查代码质量)、可维护性和重构指导时使用。
- systematic-debugging:当工作流还需要在提出修复方案前进行根因调试时使用。
- development-workflow:当工作流还需要规划、质量关卡和交付跟踪时使用。
评论
支持 Markdown