本文旨在解决PHP中从不同模型上下文调用受保护构造函数时出现的常见错误。文章将深入剖析受保护构造函数的设计意图及其在跨类访问时引发的问题。我们将探讨通过继承并提供公共构造函数来规避此限制的实用方法,并讨论在PHP面向对象设计中,何时应使用受保护构造函数以及如何实现模型间安全、高效的交互,以避免此类运行时错误。
在PHP的面向对象编程中,构造函数 __construct() 是一个特殊方法,用于在创建对象时执行初始化操作。它的访问修饰符(public、protected、private)决定了谁可以调用它。
当你在一个类(例如 myClassA2)中尝试实例化或访问另一个类(例如 myClassA1)的受保护构造函数时,如果 myClassA2 不是 myClassA1 的子类,PHP解释器会抛出 Call to protected __construct() from context 错误。这是因为 myClassA2 并没有被授权访问 myClassA1 的受保护成员。
假设我们有两个模型类 myClassA1 和 myClassA2,它们都继承自 Jsonable(一个假设的基类),并且 myClassA1 的构造函数被定义为 protected:
myPropertyA1 = "Data from A1";
}
public function getWhatINeed()
{
return $this->myPropertyA1;
}
}现在,在 myClassA2 中,我们尝试加载并访问 myClassA1:
load->model("myClassA1"); // 加载myClassA1模型
// 这里的 $CI->myClassA1 实际上会尝试实例化 myClassA1,
// 而 myClassA1 的构造函数是 protected,导致错误。
$this->myClassA1Instance = $CI->myClassA1; // 这一行或
其隐式操作会触发错误
// echo $this->myClassA1Instance->getWhatINeed(); // 如果成功实例化,可以调用
}
}当 myClassA2 被实例化时,CodeIgniter 的 load->model() 方法会尝试创建 myClassA1 的实例。由于 myClassA1::__construct() 是 protected,且 myClassA2 并非 myClassA1 的子类,PHP会抛出以下错误:
PHP Call to protected myClassA1::__construct() from context 'myClassA2'
如果 myClassA1 的设计意图确实是作为基类或模板,需要通过继承来使用,那么一种直接的解决方案是创建一个新的类,它继承自 myClassA1,并提供一个公共的构造函数。这个公共构造函数可以调用父类的受保护构造函数。
load->model(),因为它会尝试直接实例化 myClassA1
$this->myClassA1Instance = new PublicMyClassA1();
echo $this->myClassA1Instance->getWhatINeed(); // 正常工作
}
}注意事项:
在许多情况下,__construct() 被设置为 protected 可能是一个设计选择的误区,或者有更合适的模式来处理。
构造函数可见性:
如果 myClassA1 旨在被其他类直接实例化和使用,那么其构造函数应为 public。 受保护的构造函数通常意味着该类不应该被外部直接 new。如果 myClassA1 只是一个普通的数据访问层或业务逻辑类,那么将其构造函数设为 public 是最直接且符合预期的做法。
示例:将 myClassA1::__construct() 改为 public
class myClassA1 extends Jsonable
{
protected $myPropertyA1;
public function __construct() // 更改为 public
{
$this->myPropertyA1 = "Data from A1";
}
public function getWhatINeed()
{
return $this->myPropertyA1;
}
}这样 myClassA2 就可以正常通过 $CI->load->model("myClassA1"); 来加载和使用 myClassA1。
依赖注入 (Dependency Injection - DI):
在面向对象设计中,一个类不应该负责创建它所依赖的对象。相反,它应该通过构造函数、setter方法或接口来接收这些依赖。这被称为依赖注入。
改进 myClassA2 的依赖管理:
class myClassA2 extends Jsonable
{
protected $myClassA1Instance;
// 通过构造函数注入 myClassA1 的实例
public function __construct(myClassA1 $a1Instance)
{
parent::__construct();
$this->myClassA1Instance = $a1Instance;
echo $this->myClassA1Instance->getWhatINeed();
}
}
// 在实例化 myClassA2 的地方(例如控制器或服务层):
// 假设 myClassA1 的构造函数已经是 public
$a1 = new myClassA1(); // 创建 myClassA1 实例
$a2 = new myClassA2($a1); // 注入 myClassA1 实例到 myClassA2这种方式使得 myClassA2 不再关心 myClassA1 如何被创建,只关心它需要一个 myClassA1 的实例。这提高了