本文目录导读:

在PHP项目中抽取公共方法,主要遵循 DRY(Don't Repeat Yourself) 原则,以下是具体的操作步骤和最佳实践:
识别可抽取的重复代码
首先找到重复的模式,
// 重复的数据库查询
$user1 = $db->query("SELECT * FROM users WHERE id = 1");
// 处理 $user1...
$user2 = $db->query("SELECT * FROM users WHERE id = 2");
// 处理 $user2...
// 重复的日期格式化
$date1 = date('Y-m-d H:i:s', strtotime($order1['created_at']));
$date2 = date('Y-m-d H:i:s', strtotime($order2['created_at']));
基础抽法方法
1 简单函数抽取
// 抽取前
$price1 = $product1['price'] * 1.2;
$discount1 = $price1 > 100 ? $price1 * 0.9 : $price1;
$price2 = $product2['price'] * 1.2;
$discount2 = $price2 > 100 ? $price2 * 0.9 : $price2;
// 抽取后
function calculateDiscountedPrice($originalPrice): float
{
$price = $originalPrice * 1.2;
return $price > 100 ? $price * 0.9 : $price;
}
$discount1 = calculateDiscountedPrice($product1['price']);
$discount2 = calculateDiscountedPrice($product2['price']);
2 带有默认参数的函数
/**
* 格式化用户显示名
*/
function formatUserName(string $firstName, ?string $lastName = ''): string
{
return trim($firstName . ' ' . $lastName);
}
// 使用
echo formatUserName('John', 'Doe'); // "John Doe"
echo formatUserName('张三'); // "张三"
面向对象抽法
1 使用静态方法(工具类)
class StringHelper
{
public static function truncate(string $text, int $length = 100, string $suffix = '...'): string
{
if (mb_strlen($text) <= $length) {
return $text;
}
return mb_substr($text, 0, $length) . $suffix;
}
public static function slugify(string $text): string
{
$text = preg_replace('/[^\p{L}\p{N}]+/u', '-', $text);
$text = trim($text, '-');
return mb_strtolower($text);
}
}
// 使用
$articleExcerpt = StringHelper::truncate($article['content'], 150);
$cleanUrl = StringHelper::slugify('My Article Title');
2 使用继承(父类提供公共方法)
abstract class BaseController
{
protected function jsonResponse($data, int $statusCode = 200): void
{
header('Content-Type: application/json');
http_response_code($statusCode);
echo json_encode($data);
exit;
}
protected function validateRequiredFields(array $data, array $required): array
{
$missing = [];
foreach ($required as $field) {
if (!isset($data[$field]) || empty($data[$field])) {
$missing[] = $field;
}
}
return $missing;
}
}
class UserController extends BaseController
{
public function create(): void
{
$input = json_decode(file_get_contents('php://input'), true);
$missing = $this->validateRequiredFields($input, ['name', 'email']);
if (!empty($missing)) {
$this->jsonResponse(['error' => '缺少字段', 'fields' => $missing], 400);
return;
}
// ... 创建用户逻辑
}
}
3 使用Trait(横向复用)
trait Loggable
{
protected function log(string $message, string $level = 'info'): void
{
$logFile = __DIR__ . '/../logs/app.log';
$logEntry = sprintf(
"[%s] [%s] %s\n",
date('Y-m-d H:i:s'),
strtoupper($level),
$message
);
file_put_contents($logFile, $logEntry, FILE_APPEND);
}
}
trait Cacheable
{
private array $cache = [];
protected function remember(string $key, callable $callback, int $ttl = 3600)
{
if (isset($this->cache[$key])) {
return $this->cache[$key];
}
$result = $callback();
$this->cache[$key] = $result;
return $result;
}
}
class DataProcessor
{
use Loggable, Cacheable;
public function processExpensiveData(int $id): array
{
return $this->remember("data_{$id}", function () use ($id) {
$this->log("Processing data for ID: {$id}");
// 处理数据的逻辑...
return ['id' => $id, 'processed' => true];
});
}
}
依赖注入与服务类
1 定义服务类
class EmailService
{
private string $smtpHost;
private string $apiKey;
public function __construct(string $smtpHost, string $apiKey)
{
$this->smtpHost = $smtpHost;
$this->apiKey = $apiKey;
}
public function send(string $to, string $subject, string $body): bool
{
// 发送邮件的逻辑
return true;
}
public function sendWelcomeEmail(string $email, string $name): bool
{
$subject = 'Welcome to our platform!';
$body = "Hello {$name},\n\nWelcome!";
return $this->send($email, $subject, $body);
}
}
2 使用依赖注入
class UserService
{
private EmailService $emailService;
private Database $db;
public function __construct(EmailService $emailService, Database $db)
{
$this->emailService = $emailService;
$this->db = $db;
}
public function registerUser(array $userData): User
{
// 创建用户
$user = $this->db->insert('users', $userData);
// 发送欢迎邮件
$this->emailService->sendWelcomeEmail($user['email'], $user['name']);
return $user;
}
}
高级模式:策略模式
interface PaymentStrategy
{
public function pay(float $amount): bool;
}
class CreditCardPayment implements PaymentStrategy
{
public function pay(float $amount): bool
{
echo "Processing credit card payment of {$amount}\n";
return true;
}
}
class PayPalPayment implements PaymentStrategy
{
public function pay(float $amount): bool
{
echo "Processing PayPal payment of {$amount}\n";
return true;
}
}
class PaymentHandler
{
private PaymentStrategy $strategy;
public function __construct(PaymentStrategy $strategy)
{
$this->strategy = $strategy;
}
public function processPayment(float $amount): bool
{
// 通用的验证逻辑
if ($amount <= 0) {
throw new InvalidArgumentException('Amount must be positive');
}
// 使用策略执行支付
return $this->strategy->pay($amount);
}
}
抽取公共方法的最佳实践
1 命名规范
// ✅ 好的命名
function formatCurrency(float $amount): string {}
function validateEmail(string $email): bool {}
function generateUniqueId(): string {}
// ❌ 差的命名
function doStuff(): mixed {}
function processStuff(array $data): mixed {}
function h($v): string {} // 不明确的缩写
2 参数设计原则
// ✅ 好的设计:默认值 + 类型提示
function sendNotification(
string $recipient,
string $message,
string $type = 'email',
bool $urgent = false
): bool {
// ...
}
// ❌ 避免过多的参数
function createOrder(
$userId, $productId, $quantity, $address,
$paymentMethod, $couponCode = null,
$notes = null, $giftWrapping = false
) {
// 使用参数对象代替
}
3 使用参数对象
class OrderRequest
{
public function __construct(
public readonly int $userId,
public readonly int $productId,
public readonly int $quantity,
public readonly string $address,
public readonly string $paymentMethod,
public readonly ?string $couponCode = null,
public readonly bool $giftWrapping = false
) {}
}
// 使用
function createOrder(OrderRequest $request): Order
{
// 实现逻辑
}
重构示例:完整流程
重构前(重复代码)
// UserController.php
class UserController
{
public function createUser()
{
$input = json_decode(file_get_contents('php://input'), true);
// 验证
$errors = [];
if (!isset($input['name']) || strlen($input['name']) < 2) {
$errors[] = 'Name is too short';
}
if (!isset($input['email']) || !filter_var($input['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Invalid email';
}
if (!isset($input['password']) || strlen($input['password']) < 6) {
$errors[] = 'Password must be at least 6 characters';
}
if (!empty($errors)) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'errors' => $errors]);
exit;
}
// 保存到数据库
$this->db->query("INSERT INTO users (name, email, password) VALUES (?, ?, ?)",
[$input['name'], $input['email'], password_hash($input['password'], PASSWORD_DEFAULT)]);
// 返回响应
header('Content-Type: application/json');
echo json_encode(['success' => true, 'message' => 'User created']);
}
public function updateUser($id)
{
$input = json_decode(file_get_contents('php://input'), true);
// 验证(重复!)
$errors = [];
if (isset($input['name']) && strlen($input['name']) < 2) {
$errors[] = 'Name is too short';
}
if (isset($input['email']) && !filter_var($input['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Invalid email';
}
if (isset($input['password']) && strlen($input['password']) < 6) {
$errors[] = 'Password must be at least 6 characters';
}
if (!empty($errors)) {
$this->jsonResponse(['success' => false, 'errors' => $errors], 400);
exit;
}
// 更新数据库
$this->db->query("UPDATE users SET name = ?, email = ? WHERE id = ?",
[$input['name'], $input['email'], $id]);
// 返回响应(重复)
$this->jsonResponse(['success' => true, 'message' => 'User updated']);
}
private function jsonResponse($data, $statusCode = 200)
{
header('Content-Type: application/json');
http_response_code($statusCode);
echo json_encode($data);
}
}
重构后(抽取公共方法)
// Validator.php
class Validator
{
public static function validate(array $data, array $rules): array
{
$errors = [];
foreach ($rules as $field => [$rule, $message]) {
$value = $data[$field] ?? null;
switch ($rule) {
case 'required':
if (empty($value)) {
$errors[$field][] = $message ?? "{$field} is required";
}
break;
case 'min_length':
if (isset($value) && strlen($value) < $message) {
$errors[$field][] = "{$field} must be at least {$message} characters";
}
break;
case 'email':
if (isset($value) && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$field][] = "{$field} is not a valid email";
}
break;
}
}
return $errors;
}
}
// UserService.php
class UserService
{
private Database $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function createUser(array $data): User
{
$this->db->query(
"INSERT INTO users (name, email, password) VALUES (?, ?, ?)",
[$data['name'], $data['email'], password_hash($data['password'], PASSWORD_DEFAULT)]
);
return new User($this->db->lastInsertId(), $data);
}
public function updateUser(int $id, array $data): bool
{
$fields = [];
$values = [];
foreach (['name', 'email', 'password'] as $field) {
if (isset($data[$field])) {
$fields[] = "{$field} = ?";
$values[] = $field === 'password' ? password_hash($data[$field], PASSWORD_DEFAULT) : $data[$field];
}
}
if (empty($fields)) {
return false;
}
$values[] = $id;
$sql = "UPDATE users SET " . implode(', ', $fields) . " WHERE id = ?";
return $this->db->query($sql, $values);
}
}
// UserController.php(重构后)
class UserController extends BaseController
{
private UserService $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function createUser(): void
{
$input = $this->getJsonInput();
$rules = [
'name' => ['required', 'min_length' => 2],
'email' => ['required', 'email'],
'password' => ['required', 'min_length' => 6],
];
$errors = Validator::validate($input, $rules);
if (!empty($errors)) {
$this->jsonResponse(['success' => false, 'errors' => $errors], 400);
return;
}
try {
$user = $this->userService->createUser($input);
$this->jsonResponse(['success' => true, 'user' => $user->toArray()], 201);
} catch (\Exception $e) {
$this->jsonResponse(['success' => false, 'message' => $e->getMessage()], 500);
}
}
public function updateUser(int $id): void
{
$input = $this->getJsonInput();
$rules = [
'name' => ['min_length' => 2],
'email' => ['email'],
'password' => ['min_length' => 6],
];
$errors = Validator::validate($input, $rules);
if (!empty($errors)) {
$this->jsonResponse(['success' => false, 'errors' => $errors], 400);
return;
}
try {
$this->userService->updateUser($id, $input);
$this->jsonResponse(['success' => true, 'message' => 'User updated']);
} catch (\Exception $e) {
$this->jsonResponse(['success' => false, 'message' => $e->getMessage()], 500);
}
}
}
抽取公共方法的关键步骤:
- 识别重复:找到代码中相同或类似的逻辑块
- 分析抽象:确定哪些部分是变化的,哪些是固定的
- 选择方式:根据场景选择函数、静态方法、继承、Trait或服务类
- 参数设计:考虑可扩展性,使用默认值和类型提示
- 测试确保:重构后确保功能不变
不要过早抽象,三次原则”比较合理——在代码重复出现三次后才进行抽象。