PHP 8.2.20发布!

神奇的方法

Magic方法是覆盖PHP默认操作的特殊方法对对象执行某些操作时。

注意安全

所有以开头的方法名称__由PHP保留。因此,不建议使用此类方法名,除非重写PHP的行为。

以下方法名称被认为是神奇的:__构造(),__destruction(),__调用(),__调用静态(),__获取(),__设置(),__发行(),__取消设置(),__睡眠(),__唤醒(),__序列化(),__取消序列化(),__toString(),__调用(),__设置状态(),__克隆()、和__调试信息().

警告

所有魔法方法,除了__construct(),__destruction()、和__克隆(),必须被宣布为公众的,否则为E_警告发出。在PHP 8.0.0之前,没有针对神奇方法发出诊断__睡眠(),__唤醒(),__序列化(),__取消序列化()、和__设置状态().

警告

如果在魔术方法的定义中使用了类型声明,那么它们必须与本文档中描述的签名相同。否则,将发出致命错误。在PHP 8.0.0之前,没有发出诊断。然而,__construct()__destruction()不得声明返回类型;否则将发出致命错误。

__睡眠()__唤醒()

公众的 __睡眠():阵列
公众的 __唤醒():空隙

序列化()检查类是否具有函数神奇的名字__睡眠()。如果是,则该函数为在任何序列化之前执行。它可以清理物体并且应该返回一个包含所有变量名称的数组应该序列化的对象。如果方法没有返回任何内容,那么无效的已序列化,并且E_通知已发布。

注释:

这是不可能的__睡眠()返回的名称父类中的私有属性。这样做将导致E_通知液位错误。使用__序列化()而不是。

注释:

从PHP 8.0.0开始,返回的值不是来自__睡眠()生成警告。此前,它生成了一个通知。

预期用途__睡眠()是提交挂起数据或执行类似的清理任务。此外,该函数是如果一个非常大的对象不需要完全保存,那么它很有用。

相反,取消序列化()检查具有神奇名称的函数的存在__唤醒()。如果存在,此功能可以重建对象可能具有的任何资源。

预期用途__唤醒()是到重新建立所有可能丢失的数据库连接在序列化期间并执行其他重新初始化任务。

示例#1睡眠和醒来

<?php(电话)
连接
{
受保护的
$链接
私有的
$dsn(美元),$用户名,$密码

公共职能
__构造($dsn(美元),$用户名,$密码)
{
$这个->数据源网络=$dsn(美元)
$这个->用户名=$用户名
$这个->密码=$密码
$这个->连接();
}

私有函数
连接()
{
$这个->链接=新项目开发办公室($这个->数据源网络,$这个->用户名,$这个->密码);
}

公共职能
__睡眠()
{
返回数组(
“dsn”,'用户名','密码');
}

公共职能
__唤醒()
{
$这个->连接();
}
}
?>

__序列化()__取消序列化()

公众的 __序列化():阵列
公众的 __取消序列化(阵列 $数据):空隙

序列化()检查类是否具有函数神奇的名字__序列化()。如果是,则该函数为在任何序列化之前执行。它必须构造并返回键/值对的关联数组表示对象的序列化形式的。如果没有返回数组类型错误将被抛出。

注释:

如果两者都有__序列化()__睡眠()仅在同一对象中定义__序列化()将被调用。__睡眠()将被忽略。如果对象实现可序列化接口,接口的序列化()方法将被忽略,并且__序列化()改为使用。

预期用途__序列化()是定义序列化友好型对象的任意表示。数组的元素可能对应于对象的属性,但这不是必需的。

相反,取消序列化()检查具有神奇名称的函数的存在__取消序列化()。如果存在,此函数将传递给从返回的已还原数组__序列化()。可能然后根据需要从该数组恢复对象的属性。

注释:

如果两者都有__取消序列化()__唤醒()仅在同一对象中定义__取消序列化()将被调用。__唤醒()将被忽略。

注释:

从PHP 7.4.0开始,此功能可用。

示例#2序列化和取消序列化

<?php(电话)
连接
{
受保护的
$链接
私有的
$dsn(美元),$用户名,$密码

公共职能
__构造($dsn(美元),$用户名,$密码)
{
$这个->数据源网络=$dsn(美元)
$这个->用户名=$用户名
$这个->密码=$密码
$这个->连接();
}

私有函数
连接()
{
$这个->链接=新项目开发办公室($这个->数据源网络,$这个->用户名,$这个->密码);
}

公共职能
__序列化():数组
{
返回[
“dsn”=>$这个->数据源网络,
“用户”=>$这个->用户名,
“通过”=>$这个->密码,
];
}

公共职能
__取消序列化(数组$数据):空隙
{
$这个->数据源网络=$数据[“dsn”];
$这个->用户名=$数据['用户'];
$这个->密码=$数据[“通过”];

$这个->连接();
}
}
?>

__toString()

公众的 __toString(字符串)():一串

这个__toString()方法允许类决定当它被当作字符串处理时,它将如何反应。例如,什么echo$obj;将打印。

警告

从PHP 8.0.0开始,返回值遵循标准PHP类型语义,意味着它将被强制为一串如果可能,如果严格打字已禁用。

A类可串对象将被接受一串类型声明,如果严格打字已启用。如果需要这种行为,类型声明必须接受可串一串通过联合类型。

从PHP 8.0.0开始,任何包含__toString()方法还将隐式实现可串接口,并将从而通过该接口的类型检查。无论如何,显式实现接口是推荐。

在PHP 7.4中,返回值必须成为一串,否则为错误被抛出。

在PHP 7.4.0之前,返回的值必须成为一串,否则是致命的E_可恢复_ERROR发出。

警告

无法从中引发异常__到字符串()PHP 7.4.0之前的方法。这样做将导致致命错误。

示例#3简单示例

<?php(电话)
//声明简单类
测试类
{
公众的
$foo美元

公共职能
__构造($foo美元)
{
$这个->foo公司=$foo美元
}

公共职能
__toString(字符串)()
{
返回
$这个->foo公司
}
}

$类=新测试类(“你好”);
回声
$类
?>

上述示例将输出:

你好

__调用()

__调用( …$值):混合的

这个__调用()当脚本尝试将对象作为函数调用。

示例#4使用__调用()

<?php(电话)
可调用类
{
公共职能
__调用(x美元)
{
变量转储(x美元);
}
}
$对象=新可调用类
$对象(5);
变量转储(可调用(_C)($对象));
?>

上述示例将输出:

整数(5)布尔(true)

示例#5使用__调用()

<?php(电话)
排序
{
私有的
$键

公共职能
__构造(字符串$键)
{
$这个->钥匙=$键
}

公共职能
__调用(数组美元,数组十亿美元):整数
{
返回
美元[$这个->钥匙] <=>十亿美元[$这个->钥匙];
}
}

$个客户= [
[
“id”=>1,'第一个名称'=>“约翰”,“last_name”=>“执行”],
[
“id”=>,'第一个名称'=>“爱丽丝”,“last_name”=>“古斯塔夫”],
[
“id”=>2,'第一个名称'=>“鲍勃”,“last_name”=>“文件”]
];

//按名字对客户进行排序
usort公司($个客户,新的排序('第一个名称'));
打印(r)($个客户);

//按姓氏对客户进行排序
usort公司($个客户,新的排序(“last_name”));
打印(r)($个客户);
?>

上述示例将输出:

阵列([0]=>数组([id]=>3[first_name]=>爱丽丝[last_name]=>古斯塔夫)[1] =>数组([标识]=>2[first_name]=>鲍勃[last_name]=>菲利佩)[2] =>数组([id]=>1[first_name]=>约翰[last_name]=>完成))阵列([0]=>数组([标识]=>1[first_name]=>约翰[last_name]=>完成)[1] =>数组([标识]=>2[first_name]=>鲍勃[last_name]=>菲利佩)[2] =>数组([标识]=>3[first_name]=>爱丽丝[last_name]=>古斯塔夫))

__设置状态()

静止的 __设置_状态(阵列 $属性):对象

这个静止的方法被调用对于由导出的类变量导出(_E).

此方法的唯一参数是包含导出的表单中的属性[“属性”=>值,…].

示例#6使用__设置状态()

<?php(电话)

A类
{
公众的
$变量1
公众的
var2美元

公共静态函数
__设置状态($an_array(数组))
{
$对象=新A类
$对象->变量1=$an_array(数组)[“变量1”];
$对象->变量2=$an_array(数组)[“变量2”];
返回
$对象
}
}

美元=新A类
美元->变量1=5
美元->变量2=“foo”

十亿美元=变量导出(_E)(美元,真的);
变量转储(十亿美元);
评估(
'$c='.十亿美元.';');
变量转储($c美元);
?>

上述示例将输出:

字符串(60)“A::__set_state(数组(“变量1”=>5,“var2”=>“foo”,))"对象(A)#2(2){[“var1”]=>整数(5)[“var2”]=>字符串(3)“foo”}

注释:导出对象时,变量导出(_E)不检查是否__设置状态()由对象的类实现,因此重新导入对象将导致错误例外,如果__set_state()未实现。特别是,这会影响一些内部类。 程序员有责任验证只有对象被重新导入,其类实现了__set_state()。

__调试信息()

__调试信息():阵列

此方法由调用var_dump()倾倒时对象以获取应显示的属性。如果方法不是在对象上定义,然后定义所有公共、受保护和私有属性将显示。

示例#7使用__调试信息()

<?php(电话)
C类{
私有的
$道具

公共职能
__构造($val美元) {
$这个->支柱=$val美元
}

公共职能
__调试信息() {
返回[
“propSquared”=>$这个->支柱**2,
];
}
}

变量转储(新C类(42));
?>

上述示例将输出:

对象(C)#1(1){[“propSquared”]=>整数(1764)}
添加注释

用户贡献的笔记10条注释

jon at webignition点网
15年前
__toString()方法对于将类属性名称和值转换为数据的通用字符串表示形式(有很多选择)非常有用。我提到这一点是因为前面对__toString()的引用仅指调试使用。

我以前曾以以下方式使用__toString()方法:

-将数据持有对象表示为:
-XML格式
-原始POST数据
-GET查询字符串
-标头名称:值对

-将自定义邮件对象表示为实际的电子邮件(头,然后是正文,全部正确表示)

创建类时,请考虑有哪些可能的标准字符串表示可用,其中哪些与类的用途最相关。

能够以标准化的字符串形式表示数据持有对象,使您的内部数据表示更容易与其他应用程序以可互操作的方式共享。
tyler在夜犬队给我们打点
11个月前
请注意,从PHP 8.2开始,实现__serialize()无法控制json_encode()的输出。您仍然需要实现JsonSerializable。
jsnell在e-normous网站
15年前
在使用它从父类继承的类中定义__set_state()时要非常小心,因为将为任何子类调用静态__set_state(。如果你不小心,你最终会得到一个错误类型的对象。下面是一个示例:

<?php(电话)
A类
{
公众的
$变量1

公共静态函数
__设置_状态($an_array(数组))
{
$对象=新A类
$对象->变量1=$an_array(数组)[“变量1”];
返回
$对象
}
}

B类延伸A类{
}

十亿美元=新B类
十亿美元->变量1=5

评估(
'$new_b='.变量导出(_E)(十亿美元,真的) .';');
变量转储($新_b);
/*
对象(A)#2(1){
[“var1”]=>
整数(5)
}
*/
?>
php dot net上的kguest
7年前
__在对象上调用print_r时,也会使用debugInfo:

$cat测试.php
<?php(电话)
食品安全局{

私有的
$巴=''

公共职能
__构造($val美元) {

$这个->酒吧=$val(美元)
}

公共职能
__调试信息()
{
返回[
“栏(_B)”=>$这个->酒吧];
}
}
$fooq(美元)=新食品安全局(“q”);
打印(r)($fooq(美元));

$
php测试.php(电话)
FooQ对象
(
[
_巴] =>q个
)
$
网站制作网站上的ctamayo
3年前
由于PHP<=7.3中的一个错误,从SPL类重写__debugInfo()方法被默认忽略。

<?php(电话)

可调试延伸数组对象{
公共职能
__调试信息() {
返回[
“特殊”=>'这应该会出现'];
}
}

变量转储(新可调试());

//预期产量:
//对象(可调试)#1(1){
//[“特殊”]=>
//string(19)“应该显示”
// }

//实际产量:
//对象(可调试)#1(1){
//[“storage”:“ArrayObject”:私有]=>
//数组(0){
// }
// }

?>

错误报告:https://bugs.php.net/bug.php?id=69264
daniel dot peder在gmail dot com
6年前
http://sandbox.onlinephpfunctions.com/code/4d2cc3648aed58c0dad90c7868173a4775e5ba0c

IMHO错误或需要更改功能

提供一个对象作为数组索引并不会尝试使用__toString()方法,因此使用了一些易失性对象标识符来索引数组,这会破坏任何持久性。类型提示解决了这一问题,但除了“字符串”类型提示对对象不起作用外,自动转换为字符串应该非常直观。

PS:试图提交错误,但没有修补程序,错误被忽略,不幸的是,我没有进行C编码

<?php(电话)

商店_产品_id{

受保护的
$店铺名称
受保护的
$产品id

功能
__构造($店铺名称,$产品id){
$这个->商店名称=$店铺名称
$这个->产品id=$产品id
}

功能
__toString(字符串)(){
返回
$这个->商店名称.':'.$这个->产品id
}
}

$店铺名称='商店_A'
$产品id=123
$演示id=$店铺名称.':'.$产品id
$demo_name($demo_name)=“商店A中的某些产品”

$all_products(所有产品)= [$演示id=>$演示名称];
$pid=新商店_产品_($店铺名称,$产品id);

回声
“带有类型提示:”
回声(
$演示名称===$all_products(所有产品)[(字符串)$pid]) ?“确定”:“失败”
回声
“\n”

回声
“没有类型提示:”
回声(
$演示名称===$all_products(所有产品)[$pid]) ?“确定”:“失败”
回声
“\n”
射线RO
18年前
如果您使用Magical方法'__set()',请注意
<?php(电话)
$myobject
->测试[“myarray”] ='数据'
?>
不会出现!

为此,如果您想使用__set Method;,您必须以良好的方式进行操作。)
<?php(电话)
$myobject
->测试=数组('我的阵列'=>'数据');
?>

如果已经设置了变量,__set魔法方法就不会出现!

我的第一个解决方案是使用Caller类。
有了它,我就知道我现在使用的是哪个模块了!
但谁需要它……:]
有更好的解决方案。。。
代码如下:

<?php(电话)
呼叫者{
公众的
$呼叫方
公众的
$模块

功能
__呼叫($函数名,$个参数=阵列()){
$这个->设置模块信息();

如果(
是对象(_O)($这个->呼叫者) &&函数_存在('调用用户_取消数组'))
$返回=呼叫用户取消阵列(数组(&$这个->呼叫者,$函数名),$个参数);
其他的
触发器错误(“使用Call_user_func_array调用函数失败”,用户错误(_E));

$这个->取消设置模块信息();
返回
$返回
}

功能
__构造($呼叫者类名称=,$调用者模块名称=“电子线路板”) {
如果(
$callerClassName($callerClassName)==)
触发器错误('无类名',用户错误);

$这个->模块=$调用者模块名称

如果(
类存在(_E)($呼叫者类名称))
$这个->呼叫者=新$呼叫者类名称();
其他的
触发器错误('类不存在:“”.$呼叫者类名称.'\'',用户错误);

如果(
是对象(_O)($这个->呼叫者))
{
$这个->设置模块信息();
如果(
方法存在(_E)($这个->呼叫者,“__init”))
$这个->呼叫者->__初始化();
$这个->取消设置模块信息();
}
其他的
触发器错误('调用方没有对象!',用户错误(_E));
}

功能
__破坏() {
$这个->设置模块信息();
如果(
方法存在(_E)($这个->呼叫者,“__deinit”))
$这个->呼叫者->__脱单元();
$这个->取消设置模块信息();
}

功能
__发行($isset(美元)) {
$这个->设置模块信息();
如果(
是对象(_O)($这个->呼叫者))
$返回=设置($这个->呼叫者->{$isset(美元)});
其他的
触发器错误('调用方没有对象!',用户错误);
$这个->取消设置模块信息();
返回
$返回
}

功能
__未设置($未设置) {
$这个->设置模块信息();
如果(
是对象(_O)($这个->呼叫者)) {
如果(isset(
$这个->呼叫者->{$未设置}))
未设置(
$这个->呼叫者->{未设置的美元});
}
其他的
触发器错误('调用方没有对象!',用户错误);
$这个->取消设置模块信息();
}

功能
__套($套,$val(美元)) {
$这个->设置模块信息();
如果(
是对象(_O)($这个->呼叫者))
$这个->呼叫者->{$套} =$val美元
其他的
触发器错误('调用方没有对象!',用户错误);
$这个->取消设置模块信息();
}

功能
__得到($获得) {
$这个->设置模块信息();
如果(
是对象(_O)($这个->呼叫者)) {
如果(isset(
$这个->呼叫者->{$获得}))
$返回=$这个->呼叫者->{$获得};
其他的
$返回=
}
其他的
触发器错误('调用者不是对象!',用户错误(_E));
$这个->取消设置模块信息();
返回
$返回
}

功能
设置模块信息() {
$这个->呼叫者->模块=$这个->模块
}

功能
取消设置模块信息(){
$这个->呼叫者->模块=无效的
}
}

//这可以是配置类吗?
配置{
公众的
$模块

公众的
$测试

功能
__构造()
{
打印(
'构造函数将没有模块信息。。。改用__init()<br/>');
打印(
'--> '.打印(r)($这个->模块,1).' <--');
打印(
“<br/>”);
打印(
“<br/>”);
$这个->测试='123'
}

功能
__初始化()
{
打印(
'使用__init()<br/>');
打印(
'--> '.打印(r)($这个->模块,1).' <--');
打印(
“<br/>”);
打印(
“<br/>”);
}

功能
testFunction(测试功能)($测试=)
{
如果(
$测试!=)
$这个->测试=$测试
}
}

回声(
“<前>”);
$哇=新呼叫者('配置',“留言簿”);
打印(r)($哇->测试);
打印(
“<br/>”);
打印(
“<br/>”);
$哇->测试='456'
打印(r)($哇->测试);
打印(
“<br/>”);
打印(
“<br/>”);
$哇->testFunction(测试功能)('789');
打印(r)($哇->测试);
打印(
“<br/>”);
打印(
“<br/>”);
打印(r)($哇->模块);
回声(
“</pre>”);
?>

输出类似于:

构造函数将没有模块信息。。。改用__init()!
--> <--

使用__init()!
-->留言簿<--

123

456

789

留言簿
网络服务器dot-ch的martin dot goldinger
18年前
使用会话时,由于unserialize的性能较低,因此保持会话数据较小非常重要。每一节课都应该从这节课延伸出来。结果是,没有空值写入sessiondata。它将提高性能。

<?
类BaseObject
{
函数__sleep()
{
$vars=(数组)$this;
foreach($vars作为$key=>$val)
{
if(is_null($val))
{
取消设置($vars[$key]);
}
}
返回array_keys($vars);
}
};
?>
jeffxlevy在gmail网站
18年前
有趣的是,当__sleep()和__wakeup()与sessions()混合时会发生什么。我有一种预感,当会话数据被序列化时,当对象或任何东西存储在_session中时,就会调用__sleep。真的。调用session_start()时也有同样的预感。会调用__wakeup()吗?没错。非常有用,特别是因为我正在构建大量对象(嗯,许多简单对象存储在会话中),并且需要在“唤醒”时重新加载大量自动化任务。(例如,重新启动数据库会话/连接)。
newagedigital dot com的ddavenport
19年前
OOP的原则之一是封装,即对象应该处理自己的数据,而不是其他数据。要求基类处理子类的数据是不负责任的,也是危险的,尤其是考虑到一个类不可能知道它将以多少种方式扩展。

考虑以下。。。

<?php(电话)
SomeStupidStorageClass
{
公共职能
获取内容($位置,单位:美元) { ...东西... }
}

加密存储类延伸SomeStupidStorageClass
{
私有的
$解密块
公共职能
获取内容(百万美元,单位:美元) { ...解密... }
}
?>

如果SomeStupidStorageClass决定将其子类的数据以及自己的数据序列化,那么一部分曾经是加密的内容可以清晰地存储在存储该内容的任何位置。显然,CryptedStorageClass永远不会选择这个。。。但是它必须知道如何在不调用parent::sleep()的情况下序列化其父类的数据,或者让基类做它想做的事情。

再次考虑封装,任何类都不必知道父类如何处理自己的私有数据。它当然不必担心用户会以方便的名义找到打破访问控制的方法。

如果一个类既想拥有私有/受保护的数据,又想在序列化后幸存下来,那么它应该有自己的__sleep()方法,该方法要求父类报告自己的字段,然后在适用的情况下添加到列表中。这样。。。。

<?php(电话)

更好的级别
{
私有的
$内容

公共职能
__睡眠()
{
返回数组(
'基础数据1','基础数据2');
}

公共职能
获取内容() { ...东西... }
}

更好的派生类延伸更好的级别
{
私有的
$解密块

公共职能
__睡眠()
{
返回
起源::__睡眠();
}

公共职能
获取内容() { ...解密…}
}

?>

派生类对其数据有更好的控制,我们不必担心存储了不应该存储的内容。
到顶部