php 魔术方法
PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。
php版本:7.2.10
php 魔术方法
构造方法:__construct
PHP 5 允行开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。
<?php
class BaseClass {
function __construct() {
print "In BaseClass constructor\n";
}
}
class SubClass extends BaseClass {
function __construct() {
parent::__construct();
print "In SubClass constructor\n";
}
}
class OtherSubClass extends BaseClass {
}
// In BaseClass constructor
$obj = new BaseClass();
// In BaseClass constructor
// In SubClass constructor
$obj = new SubClass();
// In BaseClass constructor
$obj = new OtherSubClass();
为了实现向后兼容性,如果 PHP 5 在类中找不到 __construct() 函数并且也没有从父类继承一个的话,它就会尝试寻找旧式的构造函数,也就是和类同名的函数。
自 PHP 5.3.3 起,在命名空间中,与类名同名的方法不再作为构造函数。这一改变不影响不在命名空间中的类。
析构方法:__destruct
PHP 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
<?php
class MyDestructableClass {
function __construct() {
print "In constructor\n";
$this->name = "MyDestructableClass";
}
function __destruct() {
print "Destroying " . $this->name . "\n";
}
}
$obj = new MyDestructableClass();
/*
In constructor
Destroying MyDestructableClass
*/
?>
默认情况下,PHP仅仅释放对象属性所占用的内存并销毁对象相关的资源.
析构函数允许你在使用一个对象之后执行任意代码来清除内存.
当PHP确定你的脚本不再与对象相关时,析构函数将被调用.
在一个函数的命名空间内,这会发生在函数return的时候.
对于全局变量,这发生于脚本结束的时候.如果你想明确地销毁一个对象,你可以给指向该对象的变量分配任何其它值.通常将变量赋值勤为NULL或者调用unset.
和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。
析构函数即使在使用 exit() 终止脚本运行时也会被调用。在析构函数中调用 exit() 将会中止其余关闭操作的运行。
Note:
试图在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误。
重载
PHP所提供的重载(overloading)是指动态地创建类属性和方法。我们是通过魔术方法(magic methods)来实现的。
当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。本节后面将使用不可访问属性(inaccessible properties)和不可访问方法(inaccessible methods)来称呼这些未定义或不可见的类属性或方法。
所有的重载方法都必须被声明为 public。
这些魔术方法的参数都不能通过引用传递。
PHP中的重载与其它绝大多数面向对象语言不同。传统的重载是用于提供多个同名的类方法,但各方法的参数类型和个数不同。
属性重载
属性重载只能在对象中进行。在静态方法中,这些魔术方法将不会被调用。所以这些方法都不能被 声明为 static。从 PHP 5.3.0 起, 将这些魔术方法定义为 static 会产生一个警告。
__set();
语法结构:public function __set ( string $name , mixed $value ) : void
在给不可访问属性赋值时,__set() 会被调用。
因为 PHP 处理赋值运算的方式,__set() 的返回值将被忽略。
class PropertyTest {
/** 重载不能被用在已经定义的属性 */
public $declared = 1;
/** 只有从类外部访问这个属性时,重载才会发生 */
private $hidden = 2;
public function __set($name, $value)
{
echo "Setting '$name' to '$value'\n";
}
}
$obj = new PropertyTest;
$obj->a = 1;
echo "<br>";
$obj->hidden = 3;
/*
Setting 'a' to '1'
Setting 'hidden' to '3'
*/
__get();
读取不可访问属性的值时,__get() 会被调用。
class PropertyTest {
/** 被重载的数据保存在此 */
private $data = array();
/** 重载不能被用在已经定义的属性 */
public $declared = 1;
/** 只有从类外部访问这个属性时,重载才会发生 */
private $hidden = 2;
public function __set($name, $value)
{
echo "Setting '$name' to '$value'<br />";
$this->data[$name] = $value;
}
public function __get($name)
{
echo "Getting '$name'\n";
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
//如果没有 返回notice 信息
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
}
$obj = new PropertyTest;
$obj->a = 1;
$obj->hidden = 3;
echo $obj->a;
echo "<br>";
echo $obj->hidden;
echo "<br>";
echo $obj->b;
echo $obj->c = 8;//如果这样使用 将不会调用__get();
__isset();
当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
class PropertyTest {
/** 被重载的数据保存在此 */
private $data = array();
/** 重载不能被用在已经定义的属性 */
public $declared = 1;
/** 只有从类外部访问这个属性时,重载才会发生 */
private $hidden = 2;
public function __set($name, $value)
{
$this->data[$name] = $value;
}
/** PHP 5.1.0之后版本 */
public function __isset($name)
{
echo "Is '$name' set?\n";
return isset($this->data[$name]);
}
}
$obj = new PropertyTest;
$obj->a = 4;
var_dump(isset($obj->a));//存在返回 true 不存在 返回FALSE
var_dump(empty($obj->b));//为空返回TRUE 不为空 返回 FALSE
var_dump(isset($obj->declared));
/*
Is 'a' set?
D:\myphp_www\PHPTutorial\WWW\demo.php:30:boolean true
Is 'hidden' set?
D:\myphp_www\PHPTutorial\WWW\demo.php:31:boolean false
Is 'b' set?
D:\myphp_www\PHPTutorial\WWW\demo.php:32:boolean true
D:\myphp_www\PHPTutorial\WWW\demo.php:33:boolean true
*/
__unset();
当对不可访问属性调用 unset() 时,__unset() 会被调用。
class PropertyTest {
/** 被重载的数据保存在此 */
private $data = array();
/** 重载不能被用在已经定义的属性 */
public $declared = 1;
/** 只有从类外部访问这个属性时,重载才会发生 */
private $hidden = 2;
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function __isset($name)
{
return isset($this->data[$name]);
}
/** PHP 5.1.0之后版本 */
public function __unset($name)
{
echo "Unsetting '$name'\n";
unset($this->data[$name]);
}
}
$obj = new PropertyTest;
$obj->a = 4;
var_dump(isset($obj->a));//存在返回 true 不存在 返回FALSE
unset($obj->a);
var_dump(isset($obj->a));
/*
D:\myphp_www\PHPTutorial\WWW\demo.php:34:boolean true
Unsetting 'a'
D:\myphp_www\PHPTutorial\WWW\demo.php:36:boolean false
*/
下面这句话是官方文档给的提示,自己对这句话意思理解的不是很清楚,使用跟多方法测试,emply() 都可以调用__isset()方法,如之后遇到,在进行补充
在除 isset() 外的其它语言结构中无法使用重载的属性,这意味着当对一个重载的属性使用 empty() 时,重载魔术方法将不会被调用。
为避开此限制,必须将重载属性赋值到本地变量再使用 empty()。
方法重载
__call()
public __call ( string $name , array $arguments ) : mixed
在对象中调用一个不可访问或不存在的方法时,__call() 会被调用。
$name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。
<?php
class MethodTest
{
private function Test($name)
{
echo $name;
}
protected function Test2($name)
{
echo $name;
}
public function __call($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling object method '$name' "
. implode(', ', $arguments). "<br />";
}
}
$obj = new MethodTest;
$obj->runTest('in object context ');//Calling object method 'runTest' in object context
$obj->Test('in object context');//Calling object method 'Test' in object context
$obj->Test2('in object context');//Calling object method 'Test2' in object context
__callStatic()
public static __callStatic ( string $name , array $arguments ) : mixed
在静态上下文中调用一个不可访问或不存在方法时,__callStatic() 会被调用。
$name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。
<?php
class MethodTest
{
private static function Test($name)
{
echo $name;
}
protected static function Test2($name)
{
echo $name;
}
public static function __callStatic($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling static method '$name' "
. implode(', ', $arguments). "<br />";
}
}
MethodTest::runTest('in static context ');//Calling static method 'runTest' in static context
MethodTest::Test('in static context');//Calling static method 'Test' in static context
MethodTest::Test2('in static context');//Calling static method 'Test2' in static context
序列化与反序列化
__sleep()
public function __sleep ( void ) : array
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。
__sleep() 不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误。可以用 Serializable 接口来替代。
__sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。
<?php
class User
{
//类的成员属性
private $name;
private $age;
private $sex;
//构造函数
function __construct($pname, $page, $psex)
{
$this->name = $pname;
$this->age = $page;
$this->sex = $psex;
}
/*
功能:返回一个数组,选择是全部序列化还是部分成员属性序列化,如果没有该函数就表示全部序列化
在序列化的时候自动调用这个方法
*/
function __sleep(){
return array('name'); //返回一个只序列化name属性的数组
}
}
$u=new User('Androidyue',22,'Man');//实例化一个对象
echo $s = serialize($u);//O:4:"User":1:{s:10:"Username";s:10:"Androidyue";}
var_dump(unserialize($s)) ;
/*
D:\myphp_www\PHPTutorial\WWW\demo.php:28:
object(User)[2]
private 'name' => string 'Androidyue' (length=10)
private 'age' => null
private 'sex' => null
*/
__wakeup()
public function __wakeup ( void ) : void
与__sleep()相反,unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
__wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
<?php
class User{
//类的成员属性
private $name;
private $age;
private $sex;
//构造函数
function __construct($pname,$page,$psex){
$this->name=$pname;
$this->age=$page;
$this->sex=$psex;
}
function __sleep(){
return array('name'); //返回一个只序列化name属性的数组
}
function __wakeup(){
$this->name='Yue';
$this->age=23;
}
}
$u=new User('Androidyue',22,'Man');//实例化一个对象
var_dump($u);
/*
object(User)[1]
private 'name' => string 'Androidyue' (length=10)
private 'age' => int 22
private 'sex' => string 'Man' (length=3)
*/
var_dump(unserialize(serialize($u)));
/*
object(User)[2]
private 'name' => string 'Yue' (length=3)
private 'age' => int 23
private 'sex' => null
*/
对象复制 __clone()
public __clone (void) : void
对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。
当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。
当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。
<?php
class Computer{
public $cpu='intel';
//如果不设置这个魔术方法那么克隆出来的就是一模一样的,__clone函数和类的构造函数类似都是做一些初始化操作
function __clone(){
$this->cpu='AMD';//$this指的是新创建的克隆对象
}
//设置析构函数,用来比较=和clone的区别
function __destruct(){
echo '*************************************<br>';
}
}
$c1=new Computer();
//使用引用的方法在添加一个引用,因为对象的引用是存放在内存中的栈内存中,如果按指针的说法,那么这样做就是在内存中又创建了一个指针指向$c1指向的对象
//我们可以通过调用析构函数判断出来这样不是复制了对象
$c2=$c1;
//使用clone方法克隆一个对象
$c3=clone $c1;
//输出两者的信息比较
echo $c3->cpu,'<br>';
echo $c1->cpu,'<br>';
/*
AMD
intel
*************************************
*************************************
*/
类变字符串 __toString()
public function __toString ( void ) : string
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
不能在 __toString() 方法中抛出异常。这么做会导致致命错误。
<?php
// Declare a simple class
class TestClass
{
public $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
public function __toString() :string
{
echo "__toString:<br />";
return serialize($this);
}
}
$class = new TestClass('Hello');
echo $class;
/*
__toString:
O:9:"TestClass":1:{s:3:"foo";s:5:"Hello";}
*/
在 PHP 5.2.0 之前,__toString() 方法只有在直接使用于 echo 或 print 时才能生效。PHP 5.2.0 之后,则可以在任何字符串环境生效(例如通过 printf(),使用 %s 修饰符),但不能用于非字符串环境(如使用 %d 修饰符)。自 PHP 5.2.0 起,如果将一个未定义 __toString() 方法的对象转换为字符串,会产生 E_RECOVERABLE_ERROR 级别的错误。
类变函数 __invoke()
public function __invoke ([ $... ] ) : mixed
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
<?php
class CallableClass
{
function __invoke($x) {
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));//检测函数在当前环境中是否可调用
/*
int 5
boolean true
*/
导出类 __set_state()
static function __set_state ( array $properties ) : object
自 PHP 5.1.0 起当调用 var_export() 导出类时,此静态 方法会被调用。
本方法的唯一参数是一个数组,其中包含按 array(‘property’ => value, …) 格式排列的类属性。
<?php
class A
{
public $var1;
public $var2;
function test()
{
echo 'test';
}
public static function __set_state($an_array) // As of PHP 5.1.0
{
echo "__set_state ";
$obj = new A;
$obj->var1 = $an_array['var1'];
$obj->var2 = $an_array['var2'];
return $obj;
}
}
$a = new A;
$a->var1 = 5;
$a->var2 = 'foo';
eval('$b = ' . var_export($a, true) . ';');// $b = A::__set_state(array(
// 'var1' => 5,
// 'var2' => 'foo',
// ));
var_dump($b);
/*
__set_state
object(A)[2]
public 'var1' => int 5
public 'var2' => string 'foo' (length=3)
*/
调试打印 __debuginfo()
function __debugInfo ( void ):array
转储对象以获取应显示的属性时 ,var_dump()调用此方法。如果未在对象上定义该方法,则将显示所有公共属性,受保护属性和私有属性。
<?php
class A {
private $prop = 30;
protected $test = 'test';
}
class C {
private $prop = 30;
protected $test = 'test';
public function __debugInfo() {
return [
'prop' => $this->prop ** 2,
'test' => $this->test,
'debuginfo' =>true
];
}
}
//观察属性权限的区别
var_dump(new A);
/*
object(A)[1]
private 'prop' => int 30
protected 'test' => string 'test' (length=4)
*/
var_dump(new C);
/*
object(C)[2]
public 'prop' => int 900
public 'test' => string 'test' (length=4)
public 'debuginfo' => boolean true
*/