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;
}
});

它们可以像普通类一样通过构造函数传递参数,扩展其他类,实现接口和使用特性。

<?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
// 第一种方式 - 匿名类直接赋值给变量
$ano_class_obj = new class{
public
$prop1 = 'hello';
public
$prop2 = 754;
const
SETT = 'some config';

public function
getValue()
{
// 执行一些操作
return 'some returned value';
}

public function
getValueWithArgu($str)
{
// 执行一些操作
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";

// 第二种方式 - 通过定义的函数将匿名类赋值给变量
$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()
{
// 执行一些操作
return 'some returned value';
}

public function
getValueWithArgu($str)
{
// 执行一些操作
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";

// 第三种方式 - 通过构造函数向匿名类传递参数
$arg = 1; // 我们通过一些操作获得它
$config = [2, false]; // 我们通过一些操作获得它
$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()
{
// 执行一些操作
return 'some returned value';
}

public function
getValueWithArgu($str)
{
// 执行一些操作
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

//我们仍然可以直接在全局作用域访问“anon”类!
$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
function nc () {
return new class {
public static
$prop = [];
};
}
?>

获取一个新对象并更改静态属性

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

var_dump($a::$prop);
// array(1) {
// [0] =>
// string(1) "a"
// }
?>

现在获取另一个对象并更改静态属性将更改原始对象,这意味着静态属性确实是静态的

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

var_dump($b::$prop); // 与 var_dump($a::$prop); 相同
// array(2) {
// [0] =>
// string(1) "a"
// [1] =>
// string(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年前
/* 我喜欢 OneShot 类的想法。
感谢那位匿名兄弟/姐妹的精确说明
new class( $a, $b )
¯¯¯¯¯¯¯¯¯

如果您出于任何原因(例如原因:以可读的方式加载文件而不使用自动加载)正在寻找“延迟 OneShot 匿名类”,它可能看起来像这样; */

$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 "基类\n"; }

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

return
$this->childClass;
}
}
}

$base = new BaseClass();

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

/*
输出:

基类
子类
Hello
World
*/
?>

明显的缺点是,除非你定义一个接口并且子类实现它,或者子类扩展现有类,否则你无法为子类设置类型。

<?php
class ParentClass {
public function
say(string $s) : void { echo " $s\n"; }
}

class
BaseClass {
public function
__construct() { echo "基类\n"; }

public
ParentClass $childClass { set {} get {
if (!isset(
$this->childClass)) {
$this->childClass = new class extends ParentClass {
public function
__construct() { echo " 子类\n"; }
};
}

return
$this->childClass;
}
}
}

$base = new BaseClass();

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

/*
输出:

基类
子类
Hello
World
*/
?>
?>

这也可以用函数来完成,但是对我来说,使用钩子更像是在其他原生支持此功能的语言中。
To Top