You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP面向对象开发中向业务类传递PDO对象的规范实现方案咨询

报错原因分析

你将DbController的__construct()声明为protected权限,只有类自身或者继承它的子类才能调用构造方法,Employee类没有继承DbController,自然没有权限直接实例化它,就会触发这个权限报错。

你原有的「业务类继承数据库类」的方案本身不符合OOP的单一职责原则:业务类的核心功能是处理业务逻辑,不应该承担数据库连接的职责,也没必要因为要用到数据库能力就继承数据库类,推荐使用「单例模式+依赖注入」的方案,改动小且符合PHP开发规范。


具体实现方案

第一步:改造DbController为单例模式,全局复用唯一数据库连接

class DbController{
    private $serverName;
    private $userName;
    private $userPass;
    private $dbName;
    private $charSet;
    private $pdo;
    private static ?self $instance = null; // 存储单例实例

    // 构造方法改为private,禁止外部直接实例化
    private function __construct() {
        try {
            $this->serverName   = "localhost";
            $this->userName     = "blabla";
            $this->userPass     = "***";
            $this->dbName       = "blabla";
            $this->charSet      = "utf8mb4";

            $dsn = "mysql:host=".$this->serverName."; dbname=".$this->dbName."; charset=".$this->charSet;
            $pdo = new PDO($dsn, $this->userName, $this->userPass);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->pdo = $pdo;
        } catch (PDOException $err) {
            die($err->getMessage());
        }
    }

    // 单例入口,全局统一用这个方法获取数据库实例
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // 改为public,对外提供PDO实例
    public function getPdo(): PDO
    {
        return $this->pdo;
    }

    // 禁止克隆、反序列化,保证单例唯一性
    private function __clone() {}
    public function __wakeup() {}

    public function __debugInfo(){
        $properties = get_object_vars($this);
        unset($properties['serverName']);
        unset($properties['userName']);
        unset($properties['userPass']);
        unset($properties['pdo']);
        unset($properties['dbName']);
        unset($properties['charSet']);
        return $properties;
    }
}

第二步:改造业务类,通过依赖注入传入PDO实例

不需要再让业务类继承数据库类,完全解耦业务逻辑和数据库连接逻辑:

<?php
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

include_once "DbController.cls.php";

class Employee{
    public $id;
    protected $name;
    protected $username;
    protected $scoore;
    protected $dbTable = "employee";
    protected PDO $pdo;

    // 构造方法直接传入PDO实例
    public function __construct(PDO $pdo, $info = NULL) {
        $this->pdo = $pdo;
        if (isset($info)) {
            $this->id = $info["id"];
            $this->name = $info["name"];
            $this->username = $info["username"];
            $this->scoore = $info["scoore"]; 
        }
    }

    // 此处省略Setters和Getters方法
}

第三步:改造子类和接口调用逻辑

同时修正你原有子类重复调用父类构造的错误:

include_once "../classes/Employee.cls.php";
$_POST = json_decode(file_get_contents('php://input'), true);

class EmployeeManagement extends Employee{
    public function __construct(PDO $pdo) {
        $info = null;
        if (isset($_SESSION['empID'])) {
            $info = $this->fetchEmpInfo($_SESSION['empID']);
        }
        // 仅调用一次父类构造
        parent::__construct($pdo, $info);
    }

    public function signIn($username, $password){
        $retrunArray = array('code' => 0, 'msg' =>  "No Data Returned");
        $checkCredential = $this->checkCredential($username, $password);
        if ($checkCredential['code'] == 1) {
            try {
                $getEmpID = $this->pdo->prepare("SELECT `id` FROM `employee` WHERE `username`=? AND `password`=? LIMIT 1;");
                $getEmpID->execute([$username, $password]);
                $empId = $getEmpID->fetch(PDO::FETCH_ASSOC)['id'];

                $_SESSION['empID'] = $empId;

                $retrunArray['code'] = 1;
                $retrunArray['msg'] = "Erfolgreich eingeloggt";
                return $retrunArray;
                
            } catch (PDOException $err) {
                $retrunArray['code'] = 0;
                $retrunArray['msg'] = $err->getMessage();
                return $retrunArray;
            }
        } else{
            return $checkCredential;
        }
    }
}

// 请求处理逻辑
// 全局复用同一个PDO连接
$pdo = DbController::getInstance()->getPdo();
// 注入PDO实例实例化业务类
$employeeService = new EmployeeManagement($pdo);
header('Content-Type: application/json');

// 登录接口处理
if (isset($_POST["signIn"])) {
    $signIn = $employeeService->signIn($_POST["username"], $_POST["password"]);
    echo json_encode($signIn);
}

方案优势
  • 完美解决权限报错问题,不需要改动原有数据库操作的核心逻辑
  • 全局只有一个数据库连接,避免重复实例化造成的资源浪费
  • 业务类和数据库类完全解耦,后续要做读写分离、换数据库驱动、加连接池只需要修改DbController,不需要动任何业务代码
  • 依赖注入的方式方便单元测试,可以直接传入Mock的PDO实例测试业务逻辑,不需要连接真实数据库

内容的提问来源于stack exchange,提问作者Bandon Dude

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.17 22:15:08