ReflectionClass::newInstanceWithoutConstructor

(PHP 5 >= 5.4.0, PHP 7, PHP 8)

ReflectionClass::newInstanceWithoutConstructor创建新的类实例,不调用构造函数

描述

public ReflectionClass::newInstanceWithoutConstructor(): object

创建一个新的类实例,不调用构造函数。

参数

返回值

错误/异常

如果该类是内部类,无法在不调用构造函数的情况下实例化,则会抛出 ReflectionException。 此异常仅限于 final 的内部类。

参见

添加说明

用户贡献说明 7 notes

11
tom at r dot je
11 年前
应该明确的是,从 OOP 理论的角度来看,使用这种方法与 goto、eval 和单例一样,是一种非常糟糕的做法。 如果您发现需要在生产代码中使用它,那么您几乎肯定在某处做错了。 它可能偶尔对调试有用,但即使那样也暗示了最初代码的糟糕。

问题是什么?它破坏了封装。 对象可以在应用程序中存在,但可能无法履行其职责,因为它缺少依赖项。 使用此方法可以使不完整的对象在系统中存在;该对象可以处于其作者从未预料到的状态。 这是不好的,因为它会导致意想不到的事情发生!OOP 中的一个基本原则是对象对其状态完全控制,使用此方法会破坏这种保证。

n.b. 下面列出的基于注释的“依赖注入”也不是解决方法或有效的用例,因为它破坏了封装(除此之外还有其他问题!)以及正在构造的类需要通过提供注释来了解容器。
9
oliver at ananit dot de
12 年前
如果您的 PHP 版本中没有此方法,您可以使用技巧在不调用构造函数的情况下创建实例。
使用反射获取类的属性和默认值,并创建一个假的“序列化”字符串。

<?php
function createInstanceWithoutConstructor($class){
$reflector = new ReflectionClass($class);
$properties = $reflector->getProperties();
$defaults = $reflector->getDefaultProperties();

$serealized = "O:" . strlen($class) . ":\"$class\":".count($properties) .':{';
foreach (
$properties as $property){
$name = $property->getName();
if(
$property->isProtected()){
$name = chr(0) . '*' .chr(0) .$name;
} elseif(
$property->isPrivate()){
$name = chr(0) . $class. chr(0).$name;
}
$serealized .= serialize($name);
if(
array_key_exists($property->getName(),$defaults) ){
$serealized .= serialize($defaults[$property->getName()]);
} else {
$serealized .= serialize(null);
}
}
$serealized .="}";
return
unserialize($serealized);
}
?>

示例

<?php
class foo
{
public
$a = 10;
protected
$b = 2;
private
$c = "default";
protected
$d;
public function
__construct(){
$this->a = null;
$this->b = null;
$this->c = "constructed";
$this->d = 42;
}
}

var_dump(createInstanceWithoutConstructor('foo'));
?>

输出
object(foo)#6 (4) {
["a"]=>
int(10)
["b":protected]=>
int(2)
["c":"foo":private]=>
string(7) "default"
["d":protected]=>
NULL
}

我希望这对某人有帮助。
Oliver Anan
4
260 at ciemnosc
8 年前
很抱歉回复这么旧的评论,但我想要指出一些东西。

@ tom at r dot je
虽然我总体上同意你的说法,但 *确实* 有些情况下并非如此,而且由于 PHP 不允许使用多个构造函数,因此没有其他好的解决办法。

> 问题是什么?它破坏了封装。
> 对象可以在应用程序中存在,但可能无法履行其
> 职责,因为它缺少依赖项。
> 使用此方法可以使不完整的对象
> 在系统中存在;
> 该对象可以处于其作者从未预料到的状态。

如果您从某种工厂方法中使用此方法,以手动方式以与构造函数不同的方式初始化对象,那么这一点就不再有效。

考虑以下示例,其中您使用构造函数在从数据库获取对象后设置对象(例如,您需要根据某个 id 参数从另一个表中拉取某个数组)。 但您也希望能够手动创建对象(例如,用于插入数据库)。
最好的做法是使用两个不同的构造函数,但由于在 php 中不可能做到这一点,因此您需要其他方法来创建对象。

示例

<?php
// 通常情况下,这会在某个类中作为静态缓存,或者由方法 getMeta($id) 等返回的数组。
$meta = array(1337 => array('key1' => 'value1', 'key2' => 'value2'));

class
Test {
public
$id;
public
$data;
public
$meta;

public function
__construct() {
global
$meta;
if(
is_int($this->id)) $this->meta = $meta[$this->id];
}
public static function
create_empty() {
$r = new ReflectionClass(__CLASS__);
return
$r->newInstanceWithoutConstructor();
}
}
echo
"模拟 PDO::FETCH_CLASS 行为: ";
$t = Test::create_empty();
$t->meta = 1337;
$t->__construct();
var_dump($t);

echo
"空类: ";
$testItem = Test::create_empty();
// ... 这里你可以开始设置项目,例如从 XML 中读取
var_dump($testItem);

$testItem->id = 0;
$testItem->data = "一些数据";
$testItem->meta = array("somekey" => "somevalue");

echo
"设置后:";
var_dump($testItem);
?>

当然,你也可以创建空构造函数,并创建一些 init() 方法,但是这样你必须记得在每个地方都调用 init()。
你也可以创建其他方法将项目添加到数据库中,但这样你需要两个类来处理相同的数据——一个用于检索,另一个用于存储。

如果你使用工厂类(或者像上面简化示例中的工厂方法),那么能够创建完全空的对象很有用。使用完整的工厂方法,你首先会使用 TestFactory->prepare() 方法,然后调用方法设置你需要的属性,工厂会在调用 TestFactory->get() 获取已准备好的对象时将所有未初始化的变量设置为默认值。
3
alejosimon at gmail
12 年前
这是一个很好的使用该方法的第一个例子。

它实现了一个透明的解析器构造函数参数,可以实现 99% 的可重用组件。

<?php

use ReflectionClass ;

trait
TSingleton
{
/**
* 构造函数。
*/
protected function __construct() {}

/**
* 丢弃克隆的单例对象。
*/
protected function __clone() {}

/**
* 获取唯一的实例。
*
* @params 可选的多个值作为构造函数的参数。
* @return 来自调用类的对象实例。
*/
public static function getInstance()
{
static
$instance = null ;

if ( !
$instance )
{
$ref = new ReflectionClass( get_called_class() ) ;
$ctor = $ref->getConstructor() ;

// 感谢 PHP 5.4
$self = $ref->newInstanceWithoutConstructor() ;

// 魔法。
$ctor->setAccessible( true ) ;
$instance = $ctor->invokeArgs( $self, func_get_args() ) ;
}

return
$instance ;
}
}

?>
1
me [ata] thomas-lauria.de
12 年前
此新功能支持基于注解的依赖注入。
<?php

// 要注入的依赖
class dep {}

class
a {
/**
* @inject
* @var dep
*/
protected $foo;
}

class
b extends a {
/**
* @inject
* @var dep
*/
protected $bar;

public function
__construct() {
echo
"CONSTRUCTOR CALLED\n";
}
}

$ref = new ReflectionClass('b');
$inst = $ref->newInstanceWithoutConstructor();

$list = $ref->getProperties();
foreach(
$list as $prop){
/* @var $prop ReflectionProperty */
$prop->getDocComment(); // 使用 grep 搜索 @inject 和 @vars 类名
$prop->setAccessible(true);
$prop->setValue($inst, new dep());
}
if(
$const = $ref->getConstructor()) {
$constName = $const->getName();
$inst->{$constName}(); // 使用 call_user_func_array($function, $param_arr); 传递参数
}

print_r($inst);
print_r($inst->foo); // 属性仍然无法访问

输出结果:

CONSTRUCTOR CALLED
b Object
(
[
bar:protected] => dep Object
(
)

[
foo:protected] => dep Object
(
)

)
PHP 致命错误: 无法访问保护的属性 b::$foo in diTest.php on line 42
0
benoit dot wery at online dot fr
9 个月前
使用 ReflectionClass::newInstanceWithoutConstructor 和 ReflectionProperty::setValue 可以为只读提升属性设置值。例如,以下代码有效(在 PHP 8.2 上测试)

<?php

class Test
{
public function
__construct(public readonly string $name)
{}
}

$test1 = new Test('test1');
$reflectionProperty = new ReflectionProperty(Test::class, 'name');
// 下一行会抛出致命错误
$reflectionProperty->setValue($test1, 'error');

$reflectionClass = new ReflectionClass(Test::class);
$test2 = $reflectionClass->newInstanceWithoutConstructor();
$reflectionProperty->setValue($test2, 'test2');

echo
$test2->name; // 将输出 "test2"
0
ben at NOSPAM dot fanmade dot de
3 年前
这种函数应该尽量少出现在生产代码中。
当然,总有例外:)
我们库中的 UseCase 类扩展了一个抽象 UseCase,它有一个构造函数,负责进行一些设置,如果某些特定条件未满足,它甚至会抛出异常。
在 UseCase 中,权限也被定义。
简化示例
<?php
abstract class UseCase implements IUseCase
{
/** @var string[] */
protected array $requiredPermissions = [];

final public function
__construct(IPresenter $out)
{
// 一些启动检查,包括授权
}
}
?>
我们已经有了更好的解决方案,但需要一些时间才能重构,我们至少要忍受几个星期。
现在我想到在应用程序的传入请求中,在初始化单个 UseCase 之前检查权限。
所以在看到这个方法之后,我在抽象 UseCase 中引入了这个小助手
<?php
public static function getRequiredPermissions(): array
{
$class = new ReflectionClass(get_called_class());

return
$class->newInstanceWithoutConstructor()->requiredPermissions;
}
?>
现在我可以检查请求中的权限,而无需在库中进行任何更改
<?php
public function authorize(): bool
{
if (!
$this->user->hasPermissions(CreatePost::getRequiredPermissions())) {
return
false;
}
return
true;
}
?>
这也带来了一个额外的好处,即为尽快切换到新的 UseCase 类提供了更多理由 :)
To Top