PHP Conference Japan 2024

ReflectionClass::newInstanceWithoutConstructor

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

ReflectionClass::newInstanceWithoutConstructor建立一個新的類別實例而不呼叫建構子

描述

public ReflectionClass::newInstanceWithoutConstructor(): object

建立一個新的類別實例,而不呼叫建構子。

參數

傳回值

錯誤/例外

如果類別是一個無法在不呼叫建構子的情況下實例化的內部類別,則會拋出 ReflectionException。此例外僅限於 final 的內部類別。

參見

新增筆記

使用者貢獻筆記 7 個筆記

11
tom at r dot je
11 年前
應該明確指出,從 OOP 理論的角度來看,使用此方法是非常不好的實踐,就像 goto、eval 和單例一樣。如果你發現需要在生產程式碼中使用它,你幾乎肯定在某個地方做錯了。它可能偶爾對除錯有用,但即使如此也暗示了初始程式碼很差。

問題是什麼?它破壞了封裝。物件可以存在於應用程式中,但可能因為缺少依賴項而無法履行其職責。使用此方法使得不完整的物件可以存在於系統中;物件可能存在於其作者從未預期的狀態。這是不好的,因為它會導致意料之外的事情發生!OOP 的一個基本原則是物件完全控制其狀態,使用此方法會阻止這種保證。

附註:下面列出的基於註解的「依賴注入」也不是解決方案或此方法的有效用例,因為它破壞了封裝(除其他事項外!),而且正在建構的類別需要透過提供註解來了解容器。
9
oliver at ananit dot de
13 年前
如果你的 PHP 版本中沒有此方法,你可以使用一個技巧來建立一個實例而不呼叫建構子。
使用 reflection 來取得類別的屬性和預設值,並建立一個假的「序列化」字串。

<?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
13 年前
此新方法的絕佳首個用途。

它實作透明的剖析器建構函式引數,以實現 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
"已呼叫建構函式\n";
}
}

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

$list = $ref->getProperties();
foreach(
$list as $prop){
/* @var $prop ReflectionProperty */
$prop->getDocComment(); // 尋找 @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); // 屬性仍然無法存取

輸出:

已呼叫建構函式
b Object
(
[
bar:protected] => dep Object
(
)

[
foo:protected] => dep Object
(
)

)
PHP 致命錯誤: 無法在 diTest.php 的第 42 行存取受保護的屬性 b::$foo
0
benoit dot wery at online dot fr
1 年前
使用 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');
// 下一行會拋出 Fatal error
$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 有一個建構子會做一些設定,如果某些特定條件未滿足,甚至會拋出例外。
在該 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