
本文旨在解决php中尝试调用`protected __construct()`时遇到的错误。文章将深入探讨`protected`访问修饰符对构造函数的限制,并提供一种通过类继承来暴露公共构造函数的实用解决方案。此外,还将讨论构造函数可见性的最佳实践、工厂方法以及依赖注入等替代设计模式,以帮助开发者更有效地管理对象创建和类间协作。
理解 protected __construct() 的作用与限制
在PHP中,protected 访问修饰符意味着一个成员(属性或方法)只能在其声明的类及其子类中被访问。当应用于 __construct() 构造函数时,它会阻止从类外部直接通过 new ClassName() 的方式实例化该类。这种设计通常用于以下场景:
- 单例模式(Singleton Pattern):确保一个类只有一个实例,并通过一个公共的静态方法来获取该实例。
- 抽象工厂(Abstract Factory)或建造者模式(Builder Pattern):将对象的创建逻辑封装在工厂或建造者类中,而不是允许客户端直接创建。
- 强制通过工厂方法创建:要求用户通过特定的静态方法或工厂类来获取对象实例,以便在创建过程中执行额外的逻辑或验证。
- 基类设计:当一个类被设计为只能通过继承来使用,并且其构造逻辑应由子类在调用 parent::__construct() 时触发。
当您尝试在不属于其继承链的另一个类中直接实例化一个具有 protected __construct() 的类时,PHP会抛出 Call to protected __construct() from context 错误,因为外部类不具备访问该保护构造函数的权限。
问题场景分析
考虑以下两个PHP类,myClassA1 和 myClassA2:
<?php
// myClassA1.php
if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class myClassA1 extends Jsonable
{
protected $myPropertyA1; // 示例属性
protected function __construct(){
// 构造逻辑
$this->myPropertyA1 = "Data from A1";
}
public function getWhatINeed(){
return $this->myPropertyA1;
}
}
<?php
// myClassA2.php
if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class myClassA2 extends Jsonable
{
protected $myPropertyA2; // 示例属性
protected $myClassA1Instance;
function __construct() {
parent::__construct();
$CI =& get_instance();
// 尝试加载并使用 myClassA1
$CI->load->model("myClassA1");
// 这里会尝试实例化 myClassA1,但其构造函数是 protected
$this->myClassA1Instance = $CI->myClassA1; // 假设CI加载后会赋值
// 接着尝试调用方法
if ($this->myClassA1Instance) {
$this->myClassA1Instance->getWhatINeed();
}
}
}
在上述 myClassA2 的构造函数中,当CodeIgniter框架尝试通过 $CI->load->model(“myClassA1”) 来加载并实例化 myClassA1 时,由于 myClassA1 的 __construct() 方法被声明为 protected,而 myClassA2 并非 myClassA1 的子类,也无法直接访问其构造函数,因此会触发以下错误:
立即学习“PHP免费学习笔记(深入)”;
PHP Call to protected (from another model)::__construct() from context
这意味着 myClassA2 无法在当前上下文中直接创建 myClassA1 的实例。
解决方案:通过继承暴露公共构造函数
如果无法修改原始类 myClassA1 的构造函数可见性,但又需要获取其实例,一种可行的策略是通过继承来创建一个新的类(可以是匿名类),该新类提供一个公共的构造函数,并在其中调用父类的 protected __construct()。由于子类可以访问父类的 protected 成员,这种方法是有效的。
以下是具体的实现示例:
<?php
// 假设这是在 myClassA2 或其他需要实例化 myClassA1 的地方
// ...
// 1. 创建一个匿名类,继承自 myClassA1
// 2. 在匿名类中定义一个公共的 __construct() 方法
// 3. 在公共构造函数中调用父类(myClassA1)的构造函数
$myClassA1PublicInstance = new class extends myClassA1 {
public function __construct()
{
parent::__construct(); // 调用 myClassA1 的 protected __construct()
}
};
// 现在可以通过 $myClassA1PublicInstance 正常访问 myClassA1 的公共方法
$data = $myClassA1PublicInstance->getWhatINeed();
echo $data; // 输出: Data from A1
// 如果是在 CodeIgniter 环境中,可以这样集成:
class myClassA2 extends Jsonable
{
protected $myPropertyA2;
protected $myClassA1Instance;
function __construct() {
parent::__construct();
$CI =& get_instance();
// 使用匿名类来实例化 myClassA1
$this->myClassA1Instance = new class extends myClassA1 {
public function __construct()
{
parent::__construct();
}
};
// 现在可以安全地调用 myClassA1 的公共方法
if ($this->myClassA1Instance) {
$result = $this->myClassA1Instance->getWhatINeed();
// ... 使用 result ...
}
}
}
工作原理:
- new class extends myClassA1 { … } 语句创建了一个匿名类,它继承了 myClassA1 的所有功能。
- 这个匿名类内部定义了一个 public __construct() 方法。
- 在这个公共构造函数中,通过 parent::__construct() 调用了父类 myClassA1 的 protected __construct()。由于 myClassA1 是父类,其 protected 成员对其子类是可见且可访问的,因此这种调用是合法的。
- 最终,我们得到了一个 myClassA1 类型的实例(实际上是其匿名子类的实例),这个实例可以通过其公共方法进行操作。
这种方法适用于当您无法修改原始类定义,但又需要绕过其构造函数访问限制的场景。
最佳实践与替代方案
虽然上述继承方案可以解决特定问题,但在实际开发中,更推荐遵循以下最佳实践和设计模式:
-
构造函数可见性原则
- 默认 public:如果一个类旨在被直接实例化,其 __construct() 应该总是 public。这是最常见的场景。
- protected 或 private 的慎用:仅当有明确的设计意图(如单例、工厂模式、抽象基类等)时,才将 __construct() 设为 protected 或 private。这种情况下,通常会提供一个公共的静态工厂方法来获取实例。
-
工厂方法模式
当类的构造逻辑复杂,或者需要根据不同条件创建不同类型的对象时,可以采用工厂方法。如果 __construct() 是 protected 或 private,工厂方法就是获取实例的唯一途径。class MyClassWithProtectedConstructor extends Jsonable { protected function __construct() { /* ... */ } public static function createInstance(): self { // 可以在这里执行额外的逻辑或参数验证 return new self(); // 在类内部可以调用 protected __construct() } } // 使用工厂方法获取实例 $instance = MyClassWithProtectedConstructor::createInstance();登录后复制 -
依赖注入 (Dependency Injection, DI)
在现代PHP框架(如CodeIgniter 4、Laravel、Symfony等)中,推荐使用依赖注入来管理类之间的依赖关系。而不是在类内部手动 load 或 new 另一个类,而是通过构造函数参数、setter方法或接口将依赖项“注入”到类中。例如,在CodeIgniter 3中,$CI->load->model() 实际上是将模型实例挂载到 $CI 对象上。更好的做法是让框架处理依赖:
// 在 CodeIgniter 3 中,如果 myClassA1 确实是模型,且其构造函数是 public class myClassA2 extends Jsonable { protected $myClassA1Instance; function __construct() { parent::__construct(); $CI =& get_instance(); $CI->load->model("myClassA1"); // 框架会实例化 myClassA1 $this->myClassA1Instance = $CI->myClassA1; // 获取实例 // ... } }登录后复制如果 myClassA1 的构造函数确实需要 protected,并且您希望在 myClassA2 中使用它,那么您可能需要重新审视 myClassA1 的设计,或者通过 CodeIgniter 的服务容器(如果可用)来注册一个能够正确创建 myClassA1 实例的服务。
总结
protected __construct() 的使用是PHP面向对象设计中的一个重要概念,它提供了对对象创建过程的精细控制。当遇到 Call to protected __construct() 错误时,首先应检查类的设计意图。如果无法修改原始类,通过创建一个继承类并提供公共构造函数是一种有效的运行时解决方案。然而,从长远来看,遵循构造函数可见性原则,并考虑采用工厂方法或依赖注入等设计模式,将有助于构建更健壮、可维护和可测试的应用程序。理解这些概念不仅能解决当前问题,更能提升您的PHP编程和架构设计能力。
以上就是PHP中protected __construct()的调用限制与扩展解决方案的详细内容,更多请关注php中文网其它相关文章!


