PHP Conference Japan 2024

匿名類別

當需要建立簡單的、一次性的物件時,匿名類別很有用。

<?php

// 使用明確的類別
class Logger
{
public function
log($msg)
{
echo
$msg;
}
}

$util->setLogger(new Logger());

// 使用匿名類別
$util->setLogger(new class {
public function
log($msg)
{
echo
$msg;
}
});

它們可以像一般類別一樣,將參數傳遞給它們的建構子、繼承其他類別、實作介面和使用 Trait。

<?php

class SomeClass {}
interface
SomeInterface {}
trait
SomeTrait {}

var_dump(new class(10) extends SomeClass implements SomeInterface {
private
$num;

public function
__construct($num)
{
$this->num = $num;
}

use
SomeTrait;
});

上述範例將輸出

object(class@anonymous)#1 (1) {
  ["Command line code0x104c5b612":"class@anonymous":private]=>
  int(10)
}

在另一個類別中巢狀匿名類別並不會讓它存取外部類別的任何私有或受保護的方法或屬性。為了使用外部類別的受保護屬性或方法,匿名類別可以繼承外部類別。要在匿名類別中使用外部類別的私有屬性,必須透過其建構子傳遞。

<?php

class Outer
{
private
$prop = 1;
protected
$prop2 = 2;

protected function
func1()
{
return
3;
}

public function
func2()
{
return new class(
$this->prop) extends Outer {
private
$prop3;

public function
__construct($prop)
{
$this->prop3 = $prop;
}

public function
func3()
{
return
$this->prop2 + $this->prop3 + $this->func1();
}
};
}
}

echo (new
Outer)->func2()->func3();

上述範例將輸出

6

所有由相同匿名類別宣告所建立的物件,都是該類別的實例。

<?php
function anonymous_class()
{
return new class {};
}

if (
get_class(anonymous_class()) === get_class(anonymous_class())) {
echo
'same class';
} else {
echo
'different class';
}

上述範例將輸出

same class

注意事項:

請注意,匿名類別會由引擎指定名稱,如下例所示。 這個名稱應被視為實作細節,不應依賴它。

<?php
echo get_class(new class {});

上述範例的輸出會類似如下:

class@anonymous/in/oNi1A0x7f8636ad2021

唯讀匿名類別

自 PHP 8.3.0 起,`readonly` 修飾詞可以應用於匿名類別。

範例 #1 定義唯讀匿名類別

<?php

// 使用匿名類別
$util->setLogger(new readonly class('[DEBUG]') {
public function
__construct(private string $prefix)
{
}

public function
log($msg)
{
echo
$this->prefix . ' ' . $msg;
}
});
新增註解

使用者貢獻的註解 8 則註解

匿名
8 年前
以下三個範例用非常簡單易懂的方式說明了匿名類別

<?php
// First way - anonymous class assigned directly to variable
$ano_class_obj = new class{
public
$prop1 = 'hello';
public
$prop2 = 754;
const
SETT = 'some config';

public function
getValue()
{
// do some operation
return 'some returned value';
}

public function
getValueWithArgu($str)
{
// do some operation
return 'returned value is '.$str;
}
};

echo
"\n";

var_dump($ano_class_obj);
echo
"\n";

echo
$ano_class_obj->prop1;
echo
"\n";

echo
$ano_class_obj->prop2;
echo
"\n";

echo
$ano_class_obj::SETT;
echo
"\n";

echo
$ano_class_obj->getValue();
echo
"\n";

echo
$ano_class_obj->getValueWithArgu('OOP');
echo
"\n";

echo
"\n";

// Second way - anonymous class assigned to variable via defined function
$ano_class_obj_with_func = ano_func();

function
ano_func()
{
return new class {
public
$prop1 = 'hello';
public
$prop2 = 754;
const
SETT = 'some config';

public function
getValue()
{
// do some operation
return 'some returned value';
}

public function
getValueWithArgu($str)
{
// do some operation
return 'returned value is '.$str;
}
};
}

echo
"\n";

var_dump($ano_class_obj_with_func);
echo
"\n";

echo
$ano_class_obj_with_func->prop1;
echo
"\n";

echo
$ano_class_obj_with_func->prop2;
echo
"\n";

echo
$ano_class_obj_with_func::SETT;
echo
"\n";

echo
$ano_class_obj_with_func->getValue();
echo
"\n";

echo
$ano_class_obj_with_func->getValueWithArgu('OOP');
echo
"\n";

echo
"\n";

// Third way - passing argument to anonymous class via constructors
$arg = 1; // we got it by some operation
$config = [2, false]; // we got it by some operation
$ano_class_obj_with_arg = ano_func_with_arg($arg, $config);

function
ano_func_with_arg($arg, $config)
{
return new class(
$arg, $config) {
public
$prop1 = 'hello';
public
$prop2 = 754;
public
$prop3, $config;
const
SETT = 'some config';

public function
__construct($arg, $config)
{
$this->prop3 = $arg;
$this->config =$config;
}

public function
getValue()
{
// do some operation
return 'some returned value';
}

public function
getValueWithArgu($str)
{
// do some operation
return 'returned value is '.$str;
}
};
}

echo
"\n";

var_dump($ano_class_obj_with_arg);
echo
"\n";

echo
$ano_class_obj_with_arg->prop1;
echo
"\n";

echo
$ano_class_obj_with_arg->prop2;
echo
"\n";

echo
$ano_class_obj_with_arg::SETT;
echo
"\n";

echo
$ano_class_obj_with_arg->getValue();
echo
"\n";

echo
$ano_class_obj_with_arg->getValueWithArgu('OOP');
echo
"\n";

echo
"\n";
ytubeshareit at gmail dot com
7 年前
匿名類別是語法糖,可能會讓某些人感到迷惑。
「匿名」類別仍然會被解析到全域範圍,在那裡它會被自動分配一個名稱,並且每次需要該類別時,都會使用該全域類別定義。以下範例說明....

匿名類別版本...
<?php

function return_anon(){
return new class{
public static
$str="foo";
};
}
$test=return_anon();
echo
$test::$str; //輸出 foo

//我們仍然可以直接在全域範圍內訪問「匿名」類別!
$another=get_class($test); //取得自動分配的名稱
echo $another::$str; //輸出 foo
?>

以上功能與執行以下操作相同....
<?php
class I_named_this_one{
public static
$str="foo";
}
function
return_not_anon(){
return
'I_named_this_one';
}
$clzz=return_not_anon();//取得類別名稱
echo $clzz::$str;
?>
sebastian.wasser at gmail
5 年前
我想分享我對匿名類別靜態屬性的發現。

因此,給定一個像這樣的匿名類別的物件生成函式

<?php
函數 nc () {
return new class {
public static
$prop = [];
};
}
?>

取得一個新的物件並修改靜態屬性

<?php
$a
= nc();
$a::$prop[] = 'a';

var_dump($a::$prop);
// 陣列(1) {
// [0] =>
// 字串(1) "a"
// }
?>

現在取得另一個物件並修改靜態屬性將會改變原本的物件,這意味著靜態屬性是真正的靜態

<?php
$b
= nc();
$b::$prop[] = 'b';

var_dump($b::$prop); // 與 var_dump($a::$prop) 相同;
// 陣列(2) {
// [0] =>
// 字串(1) "a"
// [1] =>
// 字串(1) "b"
// }

assert($a::$prop === $b::$prop); // true
?>
joey
5 年前
唯一能對此進行類型提示的方法似乎是將其指定為物件。

如果您需要在函式中使用多個匿名類的實例,您可以使用

$class = function(string $arg):object {
return new class($arg) {
public function __construct(string $arg) {
$this->ow = $arg;
}
};
};

不過,為了結構的緣故,建議不要在單一作用域之外或跨多個檔案使用這樣的做法。然而,如果您的類別只在一個作用域內使用,那麼它可能不會造成程式碼混亂的問題。
j.m \ jamesweb \ ca
7 年前
/* 我喜歡一次性類別的想法。
感謝那位匿名的兄弟/姊妹的精確說明
new class( $a, $b )
¯¯¯¯¯¯¯¯¯

如果您因為任何原因(例如:以可讀的方式載入檔案,但不使用自動載入)正在尋找「延遲一次性匿名類別」,它看起來可能會像這樣:*/

$u = function()use(&$u){
$u = new class{private $name = 'Utils';};
};

$w = function(&$rewrite)use(&$w){
$w = null;
$rewrite = new class{private $name = 'DataUtils';};
};

// 用法;
var_dump(
array(
'延遲',
'(自毀)',
'匿名類別建立',
array(
'之前 ( $u )' => $u,
'執行中 ( $u() )' => $u(),
'之後 ( $u )' => $u,
),
0,0,
0,0,
0,0,
'延遲',
'(覆寫 && 自毀)',
'匿名類別建立',
array(
'之前 ( $w )' => $w,
'執行中 ( $w($u) )' => $w($u),
'之後 ( $w )' => $w,
'之後 ( $u )' => $u
)
)
);

// 順帶一提:糟糕,我沒通過垃圾郵件挑戰
razvan_bc at yahoo dot com
4 年前
您可以試試這些

<?php

$oracle
=&$_['nice_php'];
$_['nice_php']=(function(){
return new class{
public static function
say($msg){
echo
$msg;
}

public static function
sp(){
echo self::say(' ');
}

};
});

/*
$_['nice_php']()::say('Hello');
$_['nice_php']()::sp();
$_['nice_php']()::say('World');
$_['nice_php']()::sp();
$_['nice_php']()::say('!');
//幾乎相同的程式碼如下
*/

$oracle()::say('Hello');
$oracle()::sp();
$oracle()::say('World');
$oracle()::sp();
$oracle()::say('!');
?>
piotr at maslosoft dot com
7 年前
請注意,`get_class` 返回的類別名稱可能包含空位元組,就像我的 PHP 版本 (7.1.4) 的情況一樣。

當類別的起始行或其主體發生更改時,名稱也會更改。

是的,名稱是實作細節,不應該依賴它,但在某些少數情況下是必需的(註釋匿名類別)。
ismaelj+php at hotmail dot com
3 個月前
感謝 PHP 8.4 中新的屬性掛鉤(https://wiki.php.net/rfc/property-hooks)和匿名函式,現在我們可以在使用時才建立內部類別的實例

<?php
class BaseClass {
public function
__construct() { echo "base class\n"; }

public
$childClass { set {} get {
if (
$this->childClass === null ) {
$this->childClass = new class {
public function
__construct() { echo " child class\n"; }
public function
say(string $s) : void { echo " $s\n"; }
};
}

return
$this->childClass;
}
}
}

$base = new BaseClass();

$base->childClass->say('Hello');
$base->childClass->say('World');

/*
輸出:

base class
child class
Hello
World
*/
?>

明顯的缺點是您無法為子類別設定類型,除非您定義一個介面且子類別實作它,或者子類別繼承現有的類別

<?php
類別 ParentClass {
公開 函式
say(字串 $s) : 無傳回值 { echo " $s\n"; }
}

類別
BaseClass {
公開 函式
__construct() { echo "基底類別\n"; }

公開
ParentClass $childClass { 設定 {} 取得 {
if (!isset(
$this->childClass)) {
$this->childClass = new class extends ParentClass {
公開 函式
__construct() { echo " 子類別\n"; }
};
}

return
$this->childClass;
}
}
}

$base = new BaseClass();

$base->childClass->say('Hello');
$base->childClass->say('World');

/*
輸出:

基底類別
子類別
Hello
World
*/
?>
?>

這也可以用函式來完成,但使用鉤子(hook)對我來說更像其他原生支援此功能的語言。
To Top