PHP模型层应该怎么设计

wen PHP项目 1

本文目录导读:

PHP模型层应该怎么设计

  1. 基础模型架构
  2. 业务模型设计
  3. 业务逻辑服务层
  4. 高级模式:查询构建器
  5. 数据验证层
  6. 最佳实践总结

我来详细讲解PHP模型层的设计原则和最佳实践。

基础模型架构

单一职责原则的模型设计

<?php
// 基础模型类
abstract class BaseModel
{
    protected PDO $db;
    protected string $table;
    protected string $primaryKey = 'id';
    public function __construct(PDO $db)
    {
        $this->db = $db;
    }
    // 通用CRUD操作
    public function find(int $id): ?array
    {
        $sql = "SELECT * FROM {$this->table} WHERE {$this->primaryKey} = :id";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['id' => $id]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
    }
    public function findAll(array $conditions = []): array
    {
        $sql = "SELECT * FROM {$this->table}";
        $params = [];
        if (!empty($conditions)) {
            $where = [];
            foreach ($conditions as $field => $value) {
                $where[] = "{$field} = :{$field}";
                $params[$field] = $value;
            }
            $sql .= " WHERE " . implode(' AND ', $where);
        }
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    public function create(array $data): int
    {
        $fields = implode(', ', array_keys($data));
        $placeholders = implode(', ', array_map(fn($key) => ":{$key}", array_keys($data)));
        $sql = "INSERT INTO {$this->table} ({$fields}) VALUES ({$placeholders})";
        $stmt = $this->db->prepare($sql);
        $stmt->execute($data);
        return (int)$this->db->lastInsertId();
    }
    public function update(int $id, array $data): bool
    {
        $sets = [];
        foreach ($data as $field => $value) {
            $sets[] = "{$field} = :{$field}";
        }
        $sql = "UPDATE {$this->table} SET " . implode(', ', $sets) 
             . " WHERE {$this->primaryKey} = :id";
        $data['id'] = $id;
        $stmt = $this->db->prepare($sql);
        return $stmt->execute($data);
    }
    public function delete(int $id): bool
    {
        $sql = "DELETE FROM {$this->table} WHERE {$this->primaryKey} = :id";
        $stmt = $this->db->prepare($sql);
        return $stmt->execute(['id' => $id]);
    }
}

业务模型设计

实体模型(Entity)

<?php
class UserEntity 
{
    private ?int $id;
    private string $name;
    private string $email;
    private ?string $phone;
    private DateTime $createdAt;
    // 构造函数
    public function __construct(array $data = [])
    {
        $this->id = $data['id'] ?? null;
        $this->name = $data['name'] ?? '';
        $this->email = $data['email'] ?? '';
        $this->phone = $data['phone'] ?? null;
        $this->createdAt = isset($data['created_at']) 
            ? new DateTime($data['created_at']) 
            : new DateTime();
    }
    // Getters
    public function getId(): ?int
    {
        return $this->id;
    }
    public function getName(): string
    {
        return $this->name;
    }
    public function getEmail(): string
    {
        return $this->email;
    }
    public function getPhone(): ?string
    {
        return $this->phone;
    }
    public function getCreatedAt(): DateTime
    {
        return $this->createdAt;
    }
    // Setters with validation
    public function setName(string $name): void
    {
        if (strlen($name) < 2 || strlen($name) > 50) {
            throw new InvalidArgumentException('Name must be between 2 and 50 characters');
        }
        $this->name = $name;
    }
    public function setEmail(string $email): void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email format');
        }
        $this->email = $email;
    }
    // 转换为数组
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'phone' => $this->phone,
            'created_at' => $this->createdAt->format('Y-m-d H:i:s')
        ];
    }
}

业务模型(Repository模式)

<?php
interface UserRepositoryInterface
{
    public function findById(int $id): ?UserEntity;
    public function findByEmail(string $email): ?UserEntity;
    public function findAll(): array;
    public function save(UserEntity $user): UserEntity;
    public function delete(int $id): bool;
    public function searchUsers(string $keyword): array;
}
class UserRepository extends BaseModel implements UserRepositoryInterface
{
    protected string $table = 'users';
    public function findById(int $id): ?UserEntity
    {
        $data = $this->find($id);
        return $data ? new UserEntity($data) : null;
    }
    public function findByEmail(string $email): ?UserEntity
    {
        $sql = "SELECT * FROM {$this->table} WHERE email = :email";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['email' => $email]);
        $data = $stmt->fetch(PDO::FETCH_ASSOC);
        return $data ? new UserEntity($data) : null;
    }
    public function findAll(): array
    {
        $results = parent::findAll();
        return array_map(fn($data) => new UserEntity($data), $results);
    }
    public function save(UserEntity $user): UserEntity
    {
        $data = $user->toArray();
        if ($user->getId()) {
            $this->update($user->getId(), $data);
        } else {
            unset($data['id']);
            $id = $this->create($data);
            $user = $this->findById($id);
        }
        return $user;
    }
    public function searchUsers(string $keyword): array
    {
        $sql = "SELECT * FROM {$this->table} 
                WHERE name LIKE :keyword 
                OR email LIKE :keyword";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['keyword' => "%{$keyword}%"]);
        $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
        return array_map(fn($data) => new UserEntity($data), $results);
    }
}

业务逻辑服务层

<?php
class UserService
{
    private UserRepositoryInterface $userRepository;
    private ValidationService $validator;
    private EventDispatcher $eventDispatcher;
    public function __construct(
        UserRepositoryInterface $userRepository,
        ValidationService $validator,
        EventDispatcher $eventDispatcher
    ) {
        $this->userRepository = $userRepository;
        $this->validator = $validator;
        $this->eventDispatcher = $eventDispatcher;
    }
    public function registerUser(array $userData): UserEntity
    {
        // 数据验证
        $this->validator->validate($userData, [
            'name' => 'required|min:2|max:50',
            'email' => 'required|email|unique:users',
            'password' => 'required|min:8|confirmed'
        ]);
        // 检查邮箱唯一性
        if ($this->userRepository->findByEmail($userData['email'])) {
            throw new DuplicateEmailException('Email already exists');
        }
        // 密码加密
        $userData['password_hash'] = password_hash(
            $userData['password'], 
            PASSWORD_BCRYPT
        );
        unset($userData['password']);
        // 创建用户实体
        $user = new UserEntity($userData);
        // 保存用户
        $savedUser = $this->userRepository->save($user);
        // 触发事件
        $this->eventDispatcher->dispatch(
            new UserRegisteredEvent($savedUser)
        );
        return $savedUser;
    }
    public function updateProfile(int $userId, array $profileData): UserEntity
    {
        $user = $this->userRepository->findById($userId);
        if (!$user) {
            throw new UserNotFoundException('User not found');
        }
        // 更新字段
        if (isset($profileData['name'])) {
            $user->setName($profileData['name']);
        }
        if (isset($profileData['phone'])) {
            $user->setPhone($profileData['phone']);
        }
        // 保存更新
        return $this->userRepository->save($user);
    }
}

高级模式:查询构建器

<?php
class QueryBuilder
{
    private PDO $db;
    private string $from;
    private array $selects = ['*'];
    private array $wheres = [];
    private array $joins = [];
    private array $orderBys = [];
    private ?int $limit = null;
    private ?int $offset = null;
    private array $params = [];
    public function __construct(PDO $db, string $table)
    {
        $this->db = $db;
        $this->from = $table;
    }
    public function select(array $fields): self
    {
        $this->selects = $fields;
        return $this;
    }
    public function where(string $field, string $operator, $value): self
    {
        $paramName = str_replace('.', '_', $field) . '_' . count($this->wheres);
        $this->wheres[] = "{$field} {$operator} :{$paramName}";
        $this->params[$paramName] = $value;
        return $this;
    }
    public function join(string $table, string $first, string $operator, string $second): self
    {
        $this->joins[] = "JOIN {$table} ON {$first} {$operator} {$second}";
        return $this;
    }
    public function orderBy(string $field, string $direction = 'ASC'): self
    {
        $this->orderBys[] = "{$field} {$direction}";
        return $this;
    }
    public function limit(int $limit): self
    {
        $this->limit = $limit;
        return $this;
    }
    public function offset(int $offset): self
    {
        $this->offset = $offset;
        return $this;
    }
    public function get(): array
    {
        $sql = $this->buildSelect();
        $stmt = $this->db->prepare($sql);
        $stmt->execute($this->params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    private function buildSelect(): string
    {
        $sql = "SELECT " . implode(', ', $this->selects)
             . " FROM {$this->from}";
        if (!empty($this->joins)) {
            $sql .= " " . implode(' ', $this->joins);
        }
        if (!empty($this->wheres)) {
            $sql .= " WHERE " . implode(' AND ', $this->wheres);
        }
        if (!empty($this->orderBys)) {
            $sql .= " ORDER BY " . implode(', ', $this->orderBys);
        }
        if ($this->limit !== null) {
            $sql .= " LIMIT {$this->limit}";
        }
        if ($this->offset !== null) {
            $sql .= " OFFSET {$this->offset}";
        }
        return $sql;
    }
}

数据验证层

<?php
trait Validatable
{
    private array $errors = [];
    public function validate(array $rules): bool
    {
        $this->errors = [];
        foreach ($rules as $field => $validations) {
            $validations = explode('|', $validations);
            foreach ($validations as $validation) {
                // 解析验证规则
                if (strpos($validation, ':') !== false) {
                    [$rule, $parameter] = explode(':', $validation);
                } else {
                    $rule = $validation;
                    $parameter = null;
                }
                // 执行验证
                $methodName = 'validate' . ucfirst($rule);
                if (method_exists($this, $methodName)) {
                    if (!$this->$methodName($this->$field, $parameter)) {
                        $this->errors[$field][] = "Field {$field} failed {$rule} validation";
                    }
                }
            }
        }
        return empty($this->errors);
    }
    private function validateRequired($value): bool
    {
        return !empty($value);
    }
    private function validateEmail($value): bool
    {
        return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
    }
    private function validateMin($value, $min): bool
    {
        return strlen($value) >= (int)$min;
    }
    public function getErrors(): array
    {
        return $this->errors;
    }
}

最佳实践总结

设计原则

  1. 单一职责:每个模型只负责一个业务领域
  2. 依赖注入:通过构造函数注入依赖
  3. 接口编程:定义接口,实现松耦合
  4. 实体与数据分离:Entity和Repository分离

层次结构

Controller
    ↓
Service (业务逻辑)
    ↓
Repository (数据访问) 
    ↓
Entity (数据实体)

注意事项

  • 避免在模型层写SQL,使用查询构建器
  • 使用类型提示和返回类型声明
  • 实现数据库事务管理
  • 添加适当的日志记录
  • 进行单元测试覆盖

这样设计的模型层具有良好的可扩展性、可维护性和测试性。

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