博客列表 >PHP 的魔术方法

PHP 的魔术方法

Whitney的博客
Whitney的博客原创
2020年03月13日 14:45:033093浏览

一、__autoload  尝试加载未定义的类

void __autoload (string $class)   $class 待加载的类名 

没有返回值

在php.net 的tip中写到: 尽管__autoload 函数也能自动加载类和接口,但更建议使用spl_autoload_register()函数。spl_autoload_register提供了一种更加灵活的方式来实现类的自动加载(同一个应用中,可以支持任意数量的加载器,比如第三方库中的)。因此,不在建议使用__autoload()函数,在以后的版本中它可能会被弃用。

实例一:使用spl_autoload_register() 作为  __autoload()函数的替代

<?php

// function __autoload($class) {
//     include 'classes/' . $class . '.class.php';
// }

function my_autoloader($class) {
    include 'classes/' . $class . '.class.php';
}

spl_autoload_register('my_autoloader');

// 或者,自 PHP 5.3.0 起可以使用一个匿名函数
spl_autoload_register(function ($class) {
    include 'classes/' . $class . '.class.php';
});

?>

实例二:class未能加载的spl_autoload_register() 例子

<?php 
namespace Foobar;

class Foo {
    static public function test($name){
        print '['.$name.']';
    }
}

//自PHP 5.3.0 起
spl_autoload_register(__NAMESPACE__.'\Foo::test');

new InexistentClass;
?>


二、__construct 与 __destruct  构造函数与析构函数

1、__construct  

具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

注意: 如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的狗在函数中调用 parent::__construct()。如果子类没有定义狗仔函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为private的话)。

实例

<?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 {
    // inherits BaseClass's constructor
}

$obj = new BaseClass(); // In BaseClass constructor

$obj = new SubClass(); // In BaseClass constructor // In SubClass constructor

$obj = new OtherSubClass(); // In BaseClass constructor
?>

运行实例 »

点击 "运行实例" 按钮查看在线实例

2、析构函数

void __destruct(void)

析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行

注意:和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数中显式调用parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。

实例

<?php 

class MyDestructableClass {
   function __construct() {
       print "In constructor\n";
       $this->name = "MyDestructableClass";
   }

   function __destruct() {
       print "Destroying " . $this->name . "\n";
   }
}

$obj = new MyDestructableClass();

?>

运行实例 »

点击 "运行实例" 按钮查看在线实例

析构函数即使在使用exit()终止脚本运行时也会被调用。在析构函数中调用exit()将会中止其余关闭操作的运行。

三、__clone()对象复制方法

使用情景:

在多数情况下,我们并不需要完全复制一个对象来获得其中属性,但有一个情况确实需要:如果你有一个GTK窗口对象,该对象持有窗口相关的资源。你可能会想到复制一个新的窗口,保持所有属性与原来的窗口相同,但必须是一个新的对象(因为如果不是新的对象,那么一个窗口中的改变就会影响到另一个窗口)。还有一种情况:如果对象A中保存着对象B的引用,当你复制对象A时,你想其中使用的对象不再是对象B而是B的一个副本,那么你必须得到对象A的一个副本。

1、使用最简单的“=”

首先要明确的是:php的对象是以一个标识符来存储的,所以对对象的直接“赋值”行为相当于“传引用”

实例

<?php
class A {
    private $a;
    protected $b;
    public $c;

    public function d(){
        echo "A->d";
    }
}

$a1 = new A();
$a2 = $a1;
$a3 = new A();
var_dump($a1);
// object(A)#1 (3) { ["a":"A":private]=> NULL ["b":protected]=> NULL ["c"]=> NULL } 
var_dump($a2);
// object(A)#1 (3) { ["a":"A":private]=> NULL ["b":protected]=> NULL ["c"]=> NULL } 
var_dump($a3);
// object(A)#2 (3) { ["a":"A":private]=> NULL ["b":protected]=> NULL ["c"]=> NULL }


?>

运行实例 »

点击 "运行实例" 按钮查看在线实例

其中可以注意到:作为对象标识符的#n,显示$a1和$a2 其实是指向同一个对象,而$a3是另一个对象

所以,如果需要拷贝一个相同且全新的对象,不能直接通过=来赋值,否则改变了$a1中变量,就是改变了$a2中变量

2、浅拷贝

PHP5中,类中一个魔术方法__clone(),在配合clone关键字和对象使用的时候,会自动调用(如果没有显式定义,则调用空的方法)。

clone关键字的作用是,复制某一个对象形成一个对象的“浅拷贝”,然后赋值给新的对象,这个对象标识符就不同了。

实例

<?php 
class A {
    public $a;
    public $b;

    public function d(){
        echo "A->d";
    }
}

class B {
    public $d;
}

$a1 = new A();
$a1->a = '123';
$a1->b = new B();
$a2 = clone $a1;

var_dump($a1);
//object(A)#1 (2) { ["a"]=> string(3) "123" ["b"]=> object(B)#2 (1) { ["d"]=> NULL } }

var_dump($a2);
//object(A)#3 (2) { ["a"]=> string(3) "123" ["b"]=> object(B)#2 (1) { ["d"]=> NULL } }

?>

运行实例 »

点击 "运行实例" 按钮查看在线实例

可以看到,$a1与$a2明显是两个不同的对象(对象标识符不同了)。但是需要留意的一点是,“b”指向的对象标识符都是#2,证明这两个对象是相同的,这就是“浅拷贝”的特点——但有时候这两个对象确实需要相同,所以PHP的clone默认是“浅拷贝”。

为什么叫浅拷贝(shallow copy)?

因为在复制的时候,所有的属性都是“值传递”的,而上面的b属性存储的是对象标识符,所以相当于做了“引用传递”,这并不是完全的拷贝,所以称为“浅拷贝”。

3、深拷贝

使用clone关键字的时候,会自动调用旧对象的__clone()方法(然后返回拷贝的对象),所以只需要在对应的类中重写__clone()方法,使返回的对象中的“引用传递”的属性指向另一个新的对象。

实例

<?php
class A {
    public $a;
    public $b;

    public function d(){
        echo "A->d";
    }

    public function __clone(){
        $this->b = clone $this->b;
    }
}

class B {
    public $d;
}

$a1 = new A();
$a1->a = '123';
$a1->b = new B();
$a2 = clone $a1;

var_dump($a1);
//object(A)#1 (2) { ["a"]=> string(3) "123" ["b"]=> object(B)#2 (1) { ["d"]=> NULL } }

var_dump($a2);
//object(A)#3 (2) { ["a"]=> string(3) "123" ["b"]=> object(B)#4 (1) { ["d"]=> NULL } }

?>

运行实例 »

点击 "运行实例" 按钮查看在线实例

四、对象重载(__set(),__get().__isset(),__unset(),__call(),__callStatic())魔术方法)

什么是重载?

PHP所提供的“重载”(overloading)是指动态地“创建”类属性和方法。我们是通过魔术方法来实现的。

当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。

所有的重载方法都必须被声明为public。

这些魔术方法的参数都不能通过引用传递。

PHP的“重载”与其它绝大多数面向对象语言不同。传统的“重载”是用于提供多个同名的类方法,但各方法的参数类型和个数不同。

1、属性重载

public void __set(string $name,mixed $value) 在给不可访问属性赋值时,__set()会被调用。

public mixed __get(string $name) 读取不可访问属性的值时,__get()会被调用。

public bool __isset(string $name) 当对不可访问属性调用isset()或empty()时,__isset()会被调用

public void __unset(string $name) 当对不可访问属性调用unset()时,__unset()会被调用

参数$name 是指要操作的变量名称。__set()方法的$value参数指定了$name变量的值。

属性重载只能在对象中进行。在静态方法中,这些魔术方法将不会被调用。

实例

<?php
class PropertyTest {
    /**  被重载的数据保存在此  */
    private $data = array();

    /**  重载不能被用在已经定义的属性  */
    public $declared = 1;

    /**  只有从类外部访问这个属性时,重载才会发生 */
    private $hidden = 2;

    public function __set($name, $value)
    {
        echo "设置属性 '$name' 值为: '$value'\n";
        $this->data[$name] = $value;
    }

    public function __get($name)
    {
        echo "获取属性 '$name' 的值:";
        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }

        $trace = debug_backtrace();
        trigger_error(
            '未定义重载属性: ' . $name .
            ' 在 ' . $trace[0]['file'] .
            ' 行数为 ' . $trace[0]['line'],
            E_USER_NOTICE);
        return null;
    }

    /**  PHP 5.1.0之后版本 */
    public function __isset($name)
    {
        echo " 这个属性 '$name' 设置了吗?\n";
        return isset($this->data[$name]);
    }

    /**  PHP 5.1.0之后版本 */
    public function __unset($name)
    {
        echo "未设置属性 '$name'\n";
        unset($this->data[$name]);
    }

    /**  非魔术方法  */
    public function getHidden()
    {
        return $this->hidden;
    }
}

echo "<pre>\n";

$obj = new PropertyTest;

$obj->a = 1;

echo $obj->a . "\n\n";

/*设置属性 'a' 值为: '1'
获取属性 'a' 的值:1*/

var_dump(isset($obj->a));

/*这个属性 'a' 设置了吗?
    bool(true)*/

unset($obj->a);
/*未设置属性 'a'*/

var_dump(isset($obj->a));

/*这个属性 'a' 设置了吗?
    bool(false)*/


echo "\n";

echo $obj->declared . "\n\n";
// 1

echo "让我们来实验一下“隐藏”的私有属性:\n";
echo "在类中可以看到私有属性,所以 __get()没有被调用\n";
echo $obj->getHidden() . "\n";
/*让我们来实验一下“隐藏”的私有属性:
在类中可以看到私有属性,所以 __get()没有被调用
2*/

echo "私有属性在类的外部是不可访问的,所以 __get()被调用了\n";
echo $obj->hidden . "\n";
/*私有属性在类的外部是不可访问的,所以 __get()被调用了
获取属性 'hidden' 的值:

Notice:  未定义重载属性: hidden 在 E:\www\test\test.php 行数为 152 in E:\www\test\test.php on line 89
*/
?>

运行实例 »

点击 "运行实例" 按钮查看在线实例

2、方法重载

public mixed __call( string $name,array $arguments) 在对象中调用一个不可访问方法时,__call()会被调用

public static mixed __callStatic(string $name,array $arguments) 在静态上下文中调用一个不可访问的方法时,__callStatic()会被调用。

$name 参数是要调用的方法名称。$argument 参数是一个枚举数组,包含着要传递给方法$name的参数。

实例

<?php
class MethodTest
{
    public function __call($name, $arguments)
    {
        // 注意: $name 的值区分大小写
        echo "调用不存在的对象方法 '$name' ,且传递参数"
            . implode(', ', $arguments). "\n";
    }

    /**  PHP 5.3.0之后版本  */
    public static function __callStatic($name, $arguments)
    {
        // 注意: $name 的值区分大小写
        echo "调用不存在的静态对象方法 '$name' ,且传递参数"
            . implode(', ', $arguments). "\n";
    }
}

$obj = new MethodTest;
$obj->runTest('in object context','name','age');
//调用不存在的对象方法 'runTest' ,且传递参数in object context, name, age 
MethodTest::runTest('in static context');  // PHP 5.3.0之后版本
//调用不存在的静态对象方法 'runTest' ,且传递参数in static context
?>

运行实例 »

点击 "运行实例" 按钮查看在线实例

五、__sleep 和 __wakeup

serialize() 函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中多有应被序列化的变量名称的数组。如果该方法未返回任何内容,则null被序列化,并产生一个E_NOTICE级别的错误。

__sleep()方法常用语提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。

与之相反,unserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用__wakeup方法,预先准备对象需要的资源。

__wakeup()经常用在反序列化操作中,例如重新建立数据库连接,或执行其他初始化操作。

实例

<?php
class Connection{
    protected $link;
    private $server,$username,$password,$db;

    public function __contruct($server,$username,$password,$db){
        $this->server = $server;
        $this->username = $username;
        $this->password = $password;
        $this->db = $db;
        $this->connect();
    }

    private function connect(){
        $this->link = mysql_connect($this->server, $this->username,$this->password);

        mysql_select_db($this->db,$this->link);
    }

    public function __sleep(){
        return array('server','username','password','db');
    }

    public function __wakeup(){
        $this->connect();
    }
}
?>

运行实例 »

点击 "运行实例" 按钮查看在线实例

六、__toString()

__tostring()方法用于一个类被当成字符串时应怎样回应。例如:echo $obj;应该显示些什么。此方法必须返回一个字符串,否则将发出一条E_RECOVERABLE_ERROR级别的致命错误。


实例

<?php
class TestClass
{
    public $foo;

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

    public function __toString() {
        return $this->foo;
    }
}

$class = new TestClass('Hello');
echo $class;
?>

运行实例 »

点击 "运行实例" 按钮查看在线实例


声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议