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
相关产品推荐
相关产品推荐

