PHP项目重复代码如何抽取公共方法

wen PHP项目 29

本文目录导读:

PHP项目重复代码如何抽取公共方法

  1. 识别可抽取的重复代码
  2. 基础抽法方法
  3. 面向对象抽法
  4. 依赖注入与服务类
  5. 高级模式:策略模式
  6. 抽取公共方法的最佳实践
  7. 重构示例:完整流程

在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);
        }
    }
}

抽取公共方法的关键步骤:

  1. 识别重复:找到代码中相同或类似的逻辑块
  2. 分析抽象:确定哪些部分是变化的,哪些是固定的
  3. 选择方式:根据场景选择函数、静态方法、继承、Trait或服务类
  4. 参数设计:考虑可扩展性,使用默认值和类型提示
  5. 测试确保:重构后确保功能不变

不要过早抽象,三次原则”比较合理——在代码重复出现三次后才进行抽象。

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