魔术方法
- php中以 __(两个下划线)开头的类方法保留为魔术方法
- 所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀
- 魔术方法在满足某个条件时,由系统自动调用
- 总之,魔术方法就是给你一个机会,让你去通过一个中转去实现调用无权限的属性方法的处理机制(当然可以不处理)
__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() 和 __debugInfo() 等方法在php中被称为"魔术方法"。
构造函数和析构函数
__construct 构造函数
类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
__deconstruct 析构函数
析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
class MyDestructableClass {
public $name;
function __construct() {
print "In constructor\n";
$this->name = "MyDestructableClass";
}
function __destruct() {
print "Destroying " . $this->name . "\n";
}
}
$obj = new MyDestructableClass();
属性重载
- 所有的重载方法都必须被声明为 public 且不能被声明为 static,除了__callStatic( )方法
- 特例__callStatic( )方法必须声明为 public 且 static
__get( )方法
- 访问权限不允许或不存在的(对象)属性时,
__get()
魔术方法会被自动调用,且自动传参,参数值为属性名 - 实现功能,可以让对象在类 {} 外部直接访问peotected和private属性
class Human{
protected $age = 18;
public $name = 'lily';
public function __get($property){ // 自动传参 参数值为要访问的属性名
//echo '你还像访问我的',$property,'属性? 无权限或不存在 拦截~ ','<br>';
// 如果是自己人,这样设计可以让他用对象直接访问.修改.查询.销毁 没有权限的属性
if (property_exists($this,$property)){
echo $this->$property,'<br>';
}else{
echo $property,'属性不存在','<br>';
}
}
}
$lily = new Human();
echo $lily->name,'<br>';
echo $lily->age,'<br>'; // 本来没有权限访问age属性 但访问到了
echo $lily->friend,'<br>';
// 这样设计的本质还是:类内添加方法间接在 {} 外部调用,只不过魔术方法自动调用了
流程:
$lily->age——>无权——>属性重载——>__get('age')
$lily->age——>没有此属性——>属性重载——>__get('friend')
__set( )方法
- 当为权限不允许或不存在的(对象)属性赋值时,
__get()
魔术方法会被自动调用,且自动传参2个参数,参数值为属性和属性值 - 实现功能,可以让对象在类 {} 外部直接修改peotected和private属性
class Human{
protected $age = 18;
public function __set($name,$value){ // 自动传参 参数值为要访问的属性名
echo '你想设置属性',$name,'=',$value,'? 无权限或不存在 拦截~','<br>';
}
}
$lily = new Human();
$lily->age = 88; // 你想设置属性age=88? 无权限或不存在 拦截~
$lily->aaa = 111; // 你想设置属性aaa=111? 无权限或不存在 拦截~
print_r($lily);
流程:
$lily->age=88 ——>无权——>属性重载——>__get('age')
$lily->age=111 ——>没有此属性——>属性重载——>__get('friend')
__isset( )方法
- 当用 isset() 或 empty() 判断无权限或不存在的(对象)属性时, __isset( )魔术方法会被自动调用,且自动传参,参数值为属性名
- 实现功能,可以让对象在类 {} 外部直接查询修饰符为peotected和private的属性是否存在
- 实践中,也可用来保护代码,无论 isset()查什么(对象)属性 都是 true
class Human{
public $name = 'lily';
protected $age = 18;
public function __isset($name){ // 自动传参 参数值为要访问的属性名
echo '你想判断属性',$name,'是否存在? 无权限或不存在 拦截~','<br>';
}
}
$lily = new Human();
var_dump(isset($lily->name)); // true
var_dump(isset($lily->age)); // false 你想查询属性age是否存在? 无权限或不存在 拦截~
var_dump(isset($lily->wife)); // false 你想查询属性age是否存在? 无权限或不存在 拦截~
保护代码,无论 isset()查什么(对象)属性 都是 true
// 无论查什么属性 都是 true
class Human{
public $name = 'lily';
protected $age = 18;
public function __isset($name){ // 自动传参 参数值为要访问的属性名
return 1; // 注意这里
}
}
$lily = new Human();
var_dump(isset($lily->name)); // true
var_dump(isset($lily->age)); // true
var_dump(isset($lily->wife)); // true
问:isset($obj->xyz) 属性,并不能说明$obj对象中有一个xyz属性
__unset( )方法
- 当用 unset() 销毁无权限或不存在的(对象)属性时, __unset( )魔术方法会被自动调用,且自动传参,参数值为属性名
- 实现功能,可以让对象在类 {} 外部直接销毁修饰符为peotected和private的属性
class Human{
public $name = 'lily';
protected $age = 18;
public function __unset($name){ // 自动传参 参数值为要访问的属性名
echo '你想销毁属性',$name,'? 无权限或不存在 拦截~','<br>';
}
}
$lily = new Human();
unset($lily->name); // 成功销毁了对象$lily的name属性
unset($lily->age); // 你想销毁属性age? 无权限或不存在 拦截~
unset($lily->wife); // 你想销毁属性wife? 无权限或不存在 拦截~
print_r($lily);
方法重载
重载 所有的重载方法都必须被声明为 public 且不能被声明为 static
除了__callStatic( )方法必须声明为 public 且 static
__call( ) 方法
- 对象调用无权限或不存在方法时,__call() 魔术方法会被自动调用,且自动传参2个参数,参数值为方法名和方法参数 (数组形式)
- 实现功能,可以让对象在类 {} 外部直接访问修饰符为peotected和private的方法
class Human{
protected function say(){
echo "我的秘密";
}
public function __call($method,$arguments){ // 自动传参 参数值为要访问的属性名
print_r($method);
print_r($arguments);
}
}
$lily = new Human();
$lily->hello(1,2,3); // hello Array ( [0] => 1 [1] => 2 [2] => 3 )
$lily->say(); // say Array ( )
流程:
$lily->hello(1,2,3) ——>没有此方法——>方法重载——>__call('hello',Array([0] => 1 [1] => 2 [2] => 3))
$lily->say() ——>无权限——>方法重载——>__get('say',Array())
__callStatic( ) 方法
静态调用无权限或不存在方法时,__callStatic( ) 魔术方法会被自动调用,且自动传参2个参数,参数值为方法名和方法参 数 (数组形式)
class Human{
protected function say(){
echo "我的秘密";
}
public static function __callStatic($method,$arguments){ // 自动传参 参数值为要访问的属性名
print_r($method);
print_r($arguments);
}
}
$lily = new Human();
Human::hello(1,2,3); // hello Array ( [0] => 1 [1] => 2 [2] => 3 )
Human::say(); // say Array ( )
如果要写框架,这几个魔术方法大有作为
// 小案例
class Weather{
public function __call($name, $arguments){
echo $name,'天气';
}
}
$obj = new Weather();
$city = $_GET['city'];
if ($city){
$obj->$city();
}
克隆方法
__clone() 方法
- 在克隆完成时,如果定义了
__clone()
方法,__clone()
方法会自动被调用 - 如果希望在克隆时,修改某个属性可用于修改属性的值,在
__clone()
方法中修改即可 - 当我们需要将一个对象完全复制一份,保证两个对象属性和属性值一样,但他们的数据空间独立,可使用克隆
- 如果希望阻止对象被克隆,只需要修改
__clone()
魔术方法声明为private
class Foo{
public $age = 18;
function __clone(){
echo '克隆了一个新对象,并修改该对象的age属性值','<br>';
$this->age = 88;
}
}
$a = new Foo;
$b = clone $a; // 克隆
echo $b->age,'<br>'; // 88
echo $a->age,'<br>'; // 18
// $b = clone $a $b = new Foo() $b = $a $b = &$a ??
// 详见对象的赋值
如何阻止对象克隆
修改__clone魔术方法为private,可以阻止对象被克隆,常用在单例模式
class Person{
private $name;
public function __construct($name){
$this->name = $name;
}
// 修改__clone魔术方法为private,可以阻止对象被克隆
private function __clone(){}
}
$p1 = new Person('aa');
$p2 = clone $p1; // 报错
其他知识点
给对象动态添加属性?
__set( )魔术方法引发的奇怪现象==>属性的重载
class Human{
private $age = 18;
//public function __set($name, $value){}
}
$p1 = new Human();
// 对象内存是没有 wife 是不存在的,但是并没有报错,因为会触发 默认__set魔术方法
// 程序中并没有写__set魔术方法,此时系统会调用 默认的__set魔术方法来动态的增加 wife属性,权限为public
// 这种情况在java c中是不可能存在的,php就是如此灵活。
// 如何避免,程序中写上__set魔术方法什么都不做,用来覆盖默认的__set魔术方法,
$p1->wife = 'kk'; // 给p1对象新增了一个 wife属性
var_dump($p1);
删了age属性,又动态添加了age属性
class Human{
public $age = 18;
}
$p1 = new Human();
unset($p1->age);
print_r($p1); // Human Object()
$p1->age = 88;
print_r($p1); // Human Object([age]=>88)
property_exists()方法的判断机制
- 如果第一个参数是对象,先在对象中找,再到类中找
- 如果第一个参数是类,只在类中找
class Human{
public $age = 18;
}
$p1 = new Human();
if (property_exists($p1,'age')){
echo 'age 属性存在','<br>'; // age 属性存在
}else{
echo 'age 属性不存在','<br>';
}
unset($p1->age);
if (property_exists($p1,'age')){
echo 'age 属性存在','<br>'; //age 属性存在
}else{
echo 'age 属性不存在','<br>';
}
// 给对象增加wife属性 类中并没有
$p1->wife = 'kk';
if (property_exists($p1,'wife')){
echo 'wife 属性存在','<br>'; // wife 属性存在
}else{
echo 'wife 属性不存在','<br>';
}
if (property_exists('Human','wife')){
echo 'wife 属性存在','<br>'; // wife 属性不存在
}else{
echo 'wife 属性不存在','<br>';
}
__call 练习题
1.请编写一个Cat类(有名字属性),要求属性为public
2.Cat类有一个 方法 jiSuan($n1, $n2, $oper)
可以计算+ - * / 是私有的
3.在类外部,$对象名->play('jiSuan', $n1, $n2, $oper)
得到结果,注意play方法,在类中没有定义.
4.要求 play 是固定的,如果没有按规则写,则给出相应的错误提示
// 不知为何报错
class Cat{
public $name;
public $age;
public function __construct($name,$age){
$this->name = $name;
$this->age = $age;
}
private function jiSuan($n1,$n2,$oper){
switch ($oper){
case '+':
echo $n1+$n2;
break;
case '-':
echo $n1-$n2;
break;
case '*':
echo $n1*$n2;
break;
case '/':
echo $n1/$n2;
break;
default:
echo '你输入的运算符不对';
}
}
public function __call($method_name, $arguments){
var_dump($arguments);
if ($method_name == 'play'){
// 先判断是否通过play调用
if ('jiSuan' == $arguments[0]){
$this->$arguments[0]($arguments[1],$arguments[2],$arguments[3]);
}else{
echo '你访问的方法不存在';
}
}else{
echo '你的访问方式有问题';
}
}
}
$p1 = new Cat('大橘',1);
$p1->play('jiSuan',5,10,'*');