魔术方法

是指某些情况下,会自动调用的方法,称为魔术方法
php面向对象中,提供了这几个魔术方法
他们的特点 都是以双下滑线开头的

__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state() 和 __clone()

  • __construct: 构造方法
  • __destruct: 析构方法
  • __clone(): 克隆方法,当对象被克隆时,将会自动调用

接下来讲几个,在项目中,尤其是自己想写框架时,非常实用

__clone

<?php
class Human{
	public $age = 24;
	public function __clone(){
		echo "有人克隆人!假冒";
	}
}

$lisi = new Human();

$zhangsan = clone $lisi;

下面内容可直接替换原文中的 ## __call() 和 ## __callStatic() 两个空小节。

__call()

当调用对象中不存在的方法,或调用的方法权限不足(private/protected)时,PHP 会自动调用 __call()。它接收两个参数:

  • $method:被调用的方法名
  • $args:调用时传入的参数数组
<?php
class Human
{
    public $name = 'lily';

    private function money()
    {
        return '30两';
    }

    public function __call($method, $args)
    {
        echo "你调用了不存在或不可访问的方法:{$method}<br/>";
        echo "参数是:";
        print_r($args);
        echo "<br/>";
    }
}

$lily = new Human();

$lily->say('hello', 123); // 方法不存在,触发 __call
$lily->money();           // private 方法,外部调用,触发 __call

运行结果:

你调用了不存在或不可访问的方法:say
参数是:Array ( [0] => hello [1] => 123 )
你调用了不存在或不可访问的方法:money
参数是:Array ( )

实际项目中,__call() 常用于动态代理、ORM 动态查询、API SDK 等。例如:

<?php
class UserModel
{
    public function __call($method, $args)
    {
        if (strpos($method, 'findBy') === 0) {
            $field = lcfirst(substr($method, 6));
            $value = isset($args[0]) ? $args[0] : '';
            echo "SELECT * FROM user WHERE {$field} = '{$value}'";
            return;
        }

        throw new Exception("方法 {$method} 不存在");
    }
}

$user = new UserModel();
$user->findByName('lily'); // 动态拼出 findByName 对应的查询

__callStatic()

当调用类中不存在或不可访问的静态方法时,PHP 会自动调用 __callStatic()。它同样接收两个参数:方法名和参数数组。注意:__callStatic() 必须声明为 public static。

<?php
class Human
{
    private static function money()
    {
        return '30两';
    }

    public static function __callStatic($method, $args)
    {
        echo "你调用了不存在或不可访问的静态方法:{$method}<br/>";
        echo "参数是:";
        print_r($args);
        echo "<br/>";
    }
}

Human::say('hello', 123); // 静态方法不存在,触发 __callStatic
Human::money();           // private static 方法,外部调用,触发 __callStatic

运行结果:

你调用了不存在或不可访问的静态方法:say
参数是:Array ( [0] => hello [1] => 123 )
你调用了不存在或不可访问的静态方法:money
参数是:Array ( )

实际项目中,__callStatic() 常用于门面类(Facade)、静态代理等。例如:

<?php
class CacheDriver
{
    public function get($key)
    {
        return "缓存值:{$key}";
    }
}

class Cache
{
    public static function __callStatic($method, $args)
    {
        $driver = new CacheDriver();

        if (!method_exists($driver, $method)) {
            throw new Exception("缓存方法 {$method} 不存在");
        }

        return call_user_func_array([$driver, $method], $args);
    }
}

// 实际调用的是 CacheDriver 实例的 get() 方法
echo Cache::get('username'); // 缓存值:username

注意:__call() 处理对象方法调用,__callStatic() 处理静态方法调用;如果方法存在且当前作用域可访问,则不会触发这两个魔术方法。

__get()

当我们调用一个权限上不允许调用的属性时或不存在的属性时,
__get魔术方法会自动 调用,
并且自动传参,参数值是被访问的属性

<?php

/*
class Human{
	private $money = '30两';
	protected $age = 23;
	public $name = 'lily';
}

$lily = new Human();
echo $lily->name;//lily
echo $lily->age;//权限错误
echo $lily->sister;//undefined property

*/

class Human{
	private $money = '30两';
	protected $age = 23;
	public $name = 'lily';

	public function __get($property){
		echo "你想访问我的",$property,'属性 :)<br/>';
	}


}

$lily = new Human();
echo $lily->name,"<br />";//lily
echo $lily->age;//你想访问我的age属性
echo $lily->money;//你想访问我的money属性
echo $lily->sister;//你想访问我的sister属性

__set()

如下:
当为无权操作的属性赋值时,
或不存在的属性赋值时
__set方法自动调用
且自动传2个参数,属性/属性值

例如: $hmm->age = 444 --无权---> __set('age',444)

<?php

$lily->aaa = 1111;
$lily->bbb = 2222;

print_r($lily);



/*
竟然给加上了属性,
其实,对象就是一个属性集,在js中更为明显
如果这么随便就加了属性,导致这个对象的属性过多,不好管理
*/


class Stu{
	private $money = "30两";
	protected $age = 24;
	public $name = "Hmm";

	public function __set($a,$b){
		echo "你想设置我的",$a,'属性,<br/>';
		echo '且其值是',$b,'<br />';
	}
}

echo "<hr>";

$hmm = new Stu();
$hmm->aaa = 1111;
$hmm->money = '40两';
$hmm->age = 444;
print_r($hmm);
echo "<br/>";
$hmm->name = "HanMM";
print_r($hmm);

__isset() 和 __unset()

__isset() 方法
当isset()判断对象不可见的属性(protected/private/不存在)
会引发__isset()来执行

当用unset销毁对象的不可见属性时,
会引发__unset();

echo "<hr>";
class Dog{
    public $leg = 4;
    protected $bone = "猪腿骨";
    public function __isset($p){
            echo "<br/>你想判断我的",$p,'属性存不存在?';
    }

    public function __unset($p){
            echo "<br/>你想去掉我的",$p,'<br>';
    }
}

$hua = new Dog();

if (isset($hua->leg) ){
	echo "{$hua->leg}";
}


if (isset($hua->tail) ){
	echo "{$hua->tail}";
}else{
	echo "<br/>答:不存在";
}

/*
问: isset($obj->key) 属性,为真,
能说明 类有一个xyz属性吗?

答: 不能
*/

echo "<hr />";
echo "__unset测试<br/>";
print_r($hua);
unset($hua->leg);
print_r($hua);

unset($hua->tail);
unset($hua->bone);


/*
unset($hua->tail)   ----没有tail属性--> __unset('tail')
*/

案例一

<?php 

class UserModel {
    protected $email = "user@163.com";
    protected $data = array();

    public function __set($k,$v) {
            //$this->$k = $v,并没有真正赋成自己的属性
            $this->data[$k] = $v;//而是放在一个数组里
    }

    public function __get($p) {
            $p = $this->data[$p] ? $this->data[$p] : null;
            return isset($p);
    }

    public function __unset($p){
            unset($this->data[$p]);
    }

    public function __isset($p){
            return isset($this->data[$p]);
    }

    public function add(){
            $sql = 'insert into table (';
            $sql .= implode(',',array_keys($this->data));
            $sql .= ') values (\'';
            $sql .= implode("','",array_values($this->data));
            $sql .= "')";
            return $sql;
    }
}

echo "<pre>";

$userModel = new UserModel();
print_r($userModel);

$userModel->username = "lisi";
$userModel->eamil = "lisi@126.com";

print_r($userModel);


//echo $userModel->add();

unset($userModel->eamil);
print_r($userModel);

echo "</pre>";