专业编程基础技术教程

网站首页 > 基础教程 正文

PHP 常用的魔术方法

ccvgpt 2024-08-05 12:29:53 基础教程 11 ℃

变量和方法三个关键字:public protected private

var $property
public $property;
function fun(){...}
public function fun(){...}

以上都将视为public公有属性和公有方法;

PHP 常用的魔术方法

PHP5.2版本:魔术方法推荐使用公有属性public, 实例化对象也无法访问魔术方法, 如果使用private私有属性, 尽管程序运行正常, 但phpDesigner8软件会报错;

PHP5.4版本:改变了旧版本的规则, 魔术方法必须是公有属性, 否则将禁用对成员属性操作的方法及相关的功能(如isset(), unset(), clone等)

Warning: The magic method __set() must have public visibility and cannot be static in E:\wamp\apache\htdocs\20160524\ceshi.php on line 15

子类中重载父类的方法:

<?php
class person{
    var $name;
    var $sex;
    var $age;
    function __construct($name,$sex,$age){
        $this->name=$name;
        $this->sex=$sex;
        $this->age=$age;
    }
    function say(){
    		echo "我的名字叫:".$this->name."<br>性别:".$this->sex."<br>年龄:".$this->age;
    }
}
class student extends person{
    private $school;
    function __construct($school){
        //构造函数, function __construct($name,$sex,$age,$school),需要传递4个值
        parent::__construct("张三","男",26); //调用父类构造函数
        $this->school=$school;
    }
    function say(){ //重载了父类的say()方法
    echo parent::say()."<br>学校:".$this->school;
    //调用了父类的方法,这样可以不破坏父类的变量封装,如果不这样的话,父类中的变量不能为private
    }
}
$student1=new student("湖南大学"); //要与student类的构造参数数目一致
$student1->say();
?>

魔术方法:__get __set __isset __unset

在给不可访问属性赋值时, __set()会被调用。

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

当对不可访问属性调用isset()或empty()时, __isset()会被调用。

当对不可访问属性调用unset()时, __unset()会被调用。

__get()魔术方法

<?php
class Person { //声明一个人类Person,其中包含的三个成员属性被封装起来
    //下面是声明人的成员属性,全都使用了private关键字封装
    private $name; //第一个成员属性$name定义人的名子,此属性被封装
    private $sex; //第二个成员属性$sex定义人的性别,此属性被封装
    private $age; //第三个成员属性$age定义人的年龄,此属性被封装
    //声明一个构造方法,将来创建对象时,为对象的成员属性赋予初值
    function __construct($name="", $sex="男", $age=1) { //使用了默认参数
        $this->name = $name; //使用传入的参数$name为成员属性$this->name赋初值
        $this->sex = $sex; //使用传入的参数$sex为成员属性$this->sex赋初值
        $this->age = $age; //使用传入的参数$age为成员属性$this->age赋初值
		}
    //在类中添加__get()方法,在直接获取属性值时自动调用一次,以属性名作为参数传入并处理
    public function __get($propertyName) { //在方法前使用private修饰,防止对象外部调用
        if($propertyName=="sex") { //如果参数传入的是”sex”则条件成立
        		return "保密"; //不让别人获取到性别,以“保密”替代
        } else if($propertyName=="age") { //如果参数传入的是“age”则条件成立
            if($this->age > 30) //如果对象中的年龄大于30时条件成立
            		return $this->age-10; //返回对象中虚假的年龄,比真实年龄小10岁
            else //如果对象中的年龄不大于30时则执行下面代码
            		return $this->$propertyName; //让访问都可以获取到对象中真实的年龄
        } else { //如果参数传入的是其它属性名则条件成立
        		return $this->$propertyName; //对其它属性都没有限制,可以直接返回属性的值
        }
    }
}
$person1=new Person("张三", "男", 40); //通过Person类实例化的对象,并通过构造方法为属性赋初值

echo "姓名:".$person1->name."<br>"; //直接访问私有属性name,自动调用了__get()方法可以间接获取
echo "性别:".$person1->sex."<br>"; //自动调用了__get()方法,但在方法中没有返回真实属性值
echo "年龄:".$person1->age."<br>"; //自动调用了__get()方法,根据对象本身的情况会返回不同的值
?>

__set()魔术方法

<?php
class Person { //声明一个人类Person,其中包含的三个成员属性被封装起来
      //下面是声明人的成员属性,全都使用了private关键字封装
      private $name; //第一个成员属性$name定义人的名子,此属性被封装
      private $sex; //第二个成员属性$sex定义人的性别,此属性被封装
      private $age; //第三个成员属性$age定义人的年龄,此属性被封装
      //声明一个构造方法,将来创建对象时,为对象的成员属性赋予初值
      function __construct($name="", $sex="男", $age=1) {
          $this->name = $name; //使用传入的参数$name为成员属性$this->name赋初值
          $this->sex = $sex; //使用传入的参数$sex为成员属性$this->sex赋初值
          $this->age = $age; //使用传入的参数$age为成员属性$this->age赋初值
      }
      //声明魔术方法需要两个参数,真接为私有属性赋值时自动调用,并可以屏蔽一些非法赋值
      public function __set($propertyName, $propertyValue) {
          if($propertyName=="sex"){ //如果第一个参数是属性名sex则条件成立
              if(!($propertyValue == "男" || $propertyValue == "女")) //第二个参数只能是男或女
              return; //如果是非法参数返回空,则结束方法执行
              }
      		if($propertyName=="age"){ //如果第一个参数是属性名age则条件成立
              if($propertyValue > 150 || $propertyValue <0) //第二个参数只能在0到150之间的整数
                  return; //如果是非法参数返回空,则结束方法执行
           }
          //根据参数决定为那个属性被赋值,传入不同的成员属性名,赋上传入的相应的值
          $this->$propertyName = $propertyValue;
          //$this->$propertyName = $propertyValue; 先解析$propertyName变量, 然后再调用属性
      }

      //下面是声明人类的成员方法,设置为公有的可以在任何地方访问
      public function say(){ //在类中声明说话的方法,将所有的私有属性说出
      		echo "我的名子叫:".$this->name.", 性别:".$this->sex.", 我的年龄是:".$this->age."。<br>";
      }
}
$person1=new Person("张三", "男", 20);
//自动调用了__set()函数,将属性名name传给第一个参数,将属性值”李四”传给第二个参数
$person1->name="李四"; //赋值成功
//自动调用了__set()函数,将属性名sex传给第一个参数,将属性值”女”传给第二个参数
$person1->sex="女"; //赋值成功
//自动调用了__set()函数,将属性名age传给第一个参数,将属性值80传给第二个参数
$person1->age=80; //赋值成功

$person1->sex="保密"; //“保密”是一个非法值,这条语句给私有属性sex赋值失败
$person1->age=800; //800是一个非法值,这条语句私有属生age赋值失败
$person1->say(); //调用$person1对象中的say()方法,查看一下所有被重新设置的新值
?>

__isset()和__unset()魔术方法

<?php
class Person { //声明一个人类Person,其中包含的三个成员属性被封装起来
        //下面是声明人的成员属性,全都使用了private关键字封装
        private $name; //第一个成员属性$name定义人的名子,此属性被封装
        private $sex; //第二个成员属性$sex定义人的性别,此属性被封装
        private $age; //第三个成员属性$age定义人的年龄,此属性被封装
        //声明一个构造方法,将来创建对象时,为对象的成员属性赋予初值
        function __construct($name="", $sex="男", $age=1) {
        $this->name = $name; //使用传入的参数$name为成员属性$this->name赋初值
        $this->sex = $sex; //使用传入的参数$sex为成员属性$this->sex赋初值
        $this->age = $age; //使用传入的参数$age为成员属性$this->age赋初值
        }
        //当在对象外面使用isset()测定私用成员属性时,自动调用,并在内部测定扣传给外部的isset()结果
        public function __isset($propertyName) { //需要一个参数,是测定的私有属性的名称
        if($propertyName=="name") //如果参数中传入的属性名等于”name’时条件成立
        return false; //返回假,不充许在对象外部测定这个属性; return; 直接退出函数,其结果是一样
        return isset($this->$propertyName); //其它的属性都可以被测定,并返回测定的结果
        }
        //当在对象外面使用unset()方法删除私用属性时,自动被调用,并在内部把私用的成员属性删除
        public function __unset($propertyName) { //需要一个参数,是要删除的私有属性名称
        if($propertyName=="name") //如果参数中传入的属性名等于”name”时条件成立
        return; //退出方法,不充许删除对象中的name属性
        unset($this->$propertyName); //在对象的内部删除在对象外指定的私有属性
        }
        /*为何不用return unset($this->$propertyName); 是因为unset()函数返回的是空值
        void unset ( mixed $var [, mixed $... ] ) */

        public function say() //在类中声明说话的方法,将所有的私有属性说出
        {
        echo "我的名子叫:".$this->name.", 性别:".$this->sex.", 我的年龄是:".$this->age."。<br>";
        }
}
$person1=new Person("张三", "男", 40); //创建一个对象$person1,将成员属性分别赋上初值

var_dump(isset($person1->name)); //输出bool(false),不充许测定对象是否存在name属性
var_dump(isset($person1->sex)); //输出bool(true),使用isset()测定对象中存在sex私有属性
var_dump(isset($person1->age)); //输出bool(true),使用isset()测定对象中存在age私有属性
var_dump(isset($person1->id)); //输出bool(false),使用isset()测定对象中不存在id属性

unset($person1->name); //删除对象中的私有属性name,但在__unset()中不充许删除
unset($person1->sex); //删除对象中的私有属性sex,删除成功
unset($person1->age); //删除对象中的私有属性age,删除成功

$person1->say(); //对象中的sex和age属性被删除,输出:我的名子叫:张三, 性别:, 我的年龄是:。
?>

clone 克隆对象 和构造方法一样, 是对新克隆的类初始化

$this是副本(克隆后的类)的引用 $that则是原本对象(被克隆的类)使用

注意:最新版本(php5.2)已经取消了$that, 高洛峰已经证实;

<?php
class Person { //声明类Person,并在其中声明了三个成员属性,一个构造方法以及一个成员方法
    private $name; //第一个私有成员属性$name用于存储人的名子
    private $sex; //第二个私有成员属性$sex用于存储人的性别
    private $age; //第三个私有成员属性$age用于存储人的age
    function __construct($name="", $sex="", $age=1) { //构造方法在对象诞生时为成员属性赋初值
        $this->name=$name;
        $this->sex=$sex;
        $this->age=$age;
    }
    function __clone() { //声明此方法则在对象克隆时自动调用,用来为新对象重新赋值
        $this->name="我是".$that->name."的副本"; //为副本对象中的name属性重新赋值
        $this->age=10; //为副本对象中的age属性重新赋值
    }
    function say() { //一个成员方法用于打印出自己对象中全部的成员属性值
    		echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
    }
}
$p1=new Person("张三", "男", 20); //创建一个对象并通过构造方法为对象中所有成员属性赋初值
$p2=clone $p1; //使用clone克隆(复制)对象,并自动调用类中的__clone()方法
$p1->say(); //调用原对象中的说话方法,打印原对象中的全部属性值
$p2->say(); //调用副本对象中的说话方法,打印出克隆对象的全部属性值
?>

__tostring 快速获取对象的字符串表示的最便捷的公式

<?php
class TestClass { //声明一个测试类,在类中声明一个成员属性和一个__toString()方法
    private $foo; //在类中声明的一个成员方法
    function __construct($foo) { //通过构造方法传值为成员属性赋初值
        $this->foo = $foo; //为成员属性赋值
    }

    public function __toString() { //在类中定义一个__toString方法
        return $this->foo; //返回一个成员属性$foo的值
    }
}
$obj = new TestClass('Hello'); //创建一个对象并赋值给对象引用$obj
echo $obj; //直接输出对象引用则自动调用了对象中的__toString()方法输出Hello
?>

__call 在对象中调用一个不可访问方法时, __call()会被调用。

<?php
class TestClass { //声明一个测试类,在类中声明printHello()和__call()方法
    function printHello() { //声明一个方法,可以让对象成功能调用
        echo "Hello<br>"; //执行时输出一条语句
    }
    function __call($functionName, $args) { //声明此方法用来处理调用对象中不存在的方法
        echo "你所调用的函数:".$functionName."(参数:"; //输出调用不存在的方法名
        print_r($args); //输出调用不存在的方法时的参数列表
        echo ")不存在!<br>\n"; //输出符加的一些提示信息
    }
    }
    $obj=new TestClass(); //通过类TestClass实例化一个对象
    $obj->myFun("one", 2, "three"); //调用对象中不存在的方法,则自动调用了对象中的__call()方法
    $obj->otherFun(8,9); //调用对象中不存在的方法,则自动调用了对象中的__call()方法
    $obj->printHello(); //调用对象中存在的方法,可以成功调用
?>

实例:制作框架的原理

<?php
//声明一个DB类(数据库操作类)的简单操作模型
class DB {
//声明一个私有成员属性数组,主要是通过下标来定义可以参加连贯操作的全部方法名称
private $sql = array(
"field" => "",
"where" => "",
"order" => "",
"limit" => "",
"group" => "",
"having" => ""
);

//连贯操作调用field() where() order() limit() group() having()方法,组合SQL语句
function __call($methodName, $args) {
//将第一个参数(代表不存在方法的方法名称),全部转成小写方式,获取方法名称
$methodName = strtolower($methodName);

//如果调用的方法名和成员属性数组$sql下标对应上,则将第二个参数给数组中下标对应的元素
if(array_key_exists($methodName, $this->sql)) {
$this->sql[$methodName] = $args[0];
} else {
echo '调用类'.get_class($this).'中的方法'.$methodName.'()不存在';
}

//返回自己对象,则可以继续调用本对象中的方法,形成连贯操作
return $this;
}

//简单的应用,没有实际意义,只是输出连贯操作后组合的一个SQL语句,是连贯操作最后调用的一个方法
function select() {
echo "SELECT {$this->sql['field']} FROM user {$this->sql['where']} {$this->sql['order']}
{$this->sql['limit']} {$this->sql['group']} {$this->sql['having']}";
}
}

$db = new DB();

//连贯操作,也可以分为多行去连续调用多个方法
$db -> field('sex, count(sex)')
-> where('where sex in ("男", "女")')
-> group('group by sex')
-> having('having avg(age) > 25')
-> select();

//如果调用的方法不存在,也会有提示,下面演示的就是调用一个不存的方法query()
$db -> query('select * from user');

__callStatic() 用静态方式中调用一个不可访问方法时, __callStatic()会被调用。PHP 5.3.0之后版本支持

<?php
class TestClass{
public $p1 = 1;
//当B的对象调用一个不存在的静态方法的时候,就会自动调用本方法
//参数:$funcName:要调用的不存在的方法名;
//参数:$argList:是一个数组,其中存放了调用该不存在的方法时的实参
static function __callStatic($funcName, $argList){
echo "<br />正在调用类的一个不存在的静态方法" . $funcName;
echo "<br />其使用的实参数据为:";
print_r($argList);
}
}
TestClass::f1(1,2); //f1方法不存在,就会调用该类中的静态方法__callstatic
TestClass::f2(); //f2方法不存在,就会调用该类中的静态方法__callstatic
$obj = new TestClass();
//$obj->f3(3, 4, 5); //虽然静态的方法也可以用对象访问, 但是对于使用__callStatic魔术方法不管用
?>

__autoload() 自动加载类 //类名和类的文件名必须一致

自动加载机制的执行原理

答:当我们实例化一个对象时, 系统会寻找与之对应的类文件, 首先在当前文件中去寻找, 如果未找到, 则到autoload函数库中去寻找,

如果还未找到, 则报错。反之, 则直接使用。

<?php
function __autoload($className) { //在方件的上方声明一个自动加载类的方法
include("class_" . ucfirst($className) . ".php"); //在方法中使用include包含类所在的文件
}

$obj = new User(); //User类不存在则自动调用__autoload()函数,将类名“User”做为参数传入
$obj2 = new Shop(); //Shop类不存在则自动调用__autoload()函数,将类名“Shop”做为参数传入
?>

自定义的自动加载函数:

系统的自动加载函数只有一个__autoload(), 有时候略显不便, 我们有时候还需要分别定义不同的函数, 去作为自动加载函数,

以应对不同的情形, 此时就可以使用自定义加载函数。

bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )

autoload_function 欲注册的自动装载函数。如果没有提供任何参数, 则自动注册 autoload 的默认实现函数spl_autoload()。

throw 此参数设置了 autoload_function 无法成功注册时, spl_autoload_register()是否抛出异常。

prepend 如果是 true, spl_autoload_register() 会添加函数到队列之首, 而不是队列尾部。

<?php
//1,先声明要作为自定义加载函数的函数名,可以多个:
spl_autoload_register('auto1');
spl_autoload_register('auto2');
//2,分别定义这几个函数,其中写逻辑去加载需要的类文件。
function auto1($className){
echo "<h1>$className</h1>";
$file = './class/' . $className . '.class.php';
if(file_exists($file)){ //file_exists()判断文件是否存在
require_once $file;
}
}
function auto2($className){
echo "<h4>$className</h4>";
$file = './library/' . $className . '.class.php';
if(file_exists($file)){ //file_exists()判断文件是否存在
require_once $file;
}
}
//3,使用类,需要的时候就会自动依次调用这几个函数去完成加载工作。
$obj1 = new A();
$obj2 = new B();
var_dump($obj1,$obj2);
?>

序列化和反序列化

序列化:就是将一个变量的数据的"内存存在形态", 转换为"硬盘存在形态"的过程。

php中, 将一个变量数据序列化, 是使用一个固定的系统函数来完成的:serialize()

注意:序列化只能存储对象的属性值, 不能存储其方法, 因此反序列化之前要加载类;

反序列化:将之前序列化之后存储到硬盘的数据(字符串),反向恢复成原来的变量数据(内存形态)。

做法:通过这个unserialize()函数进行反序列化;

string serialize ( mixed $value )

serialize() 返回字符串, 此字符串包含了表示value的字节流, 可以存储于任何地方。

这有利于存储或传递PHP的值, 同时不丢失其类型和结构。serialize()可处理除了resource之外的任何类型。

想要将已序列化的字符串变回PHP的值, 可使用 unserialize()

mixed unserialize ( string $str )

unserialize() 对单一的已序列化的变量进行操作, 将其转换回 PHP 的值。

1 建立单独的文件 class_person.php 代码如下所示:

<?php
class Person { //声明一个Person类,包含三个成员属性和一个成员方法
private $name; //人的名子
private $sex; //人的性别
private $age; //人的年龄

function __construct($name="", $sex="", $age="") { //构造方法为成员属性赋初值
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
function say() { //这个人可以说话的方法, 说出自己的成员属性
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
}
?>

2 将对象序列化, 得到的字符串保存在文件中

<?php
require "class_Person.php"; //在本文件中包含Person类所在的脚本文件
$person=new Person("张三", "男", 20); //能过Person类创建一个对象,对象的引用名为$person
$person_string=serialize($person); //通过serialize函数将对象串行化,返一个字符串
file_put_contents("file.txt", $person_string); //将对象串行化后返回的字符串保存到file.txt文件中
?>

为何对象在写入文件时需要序列化?

答: 因为对象是复合数据类型, 而file_put_contents()第二个参数必须是字符串型

3 反序列化

<?php
require "class_Person.php"; //在本文件中包含Person类所在的脚本文件
$person_string=file_get_contents("file.txt"); //将file.txt文件中的字符串读出来并赋给变量$person_string
$person=unserialize($person_string); //进行反串行化操作,形成对象$person。
$person->say(); //调用对象中的say()方法,用来测试反串行化对象是否成功
?>

提醒:序列化和反序列化函数也适用于数组, 将数组进行序列化后存入到数据库, 从数据库中读取后使用反序列化后再读取原数组;

__sleep 和 __wakeup魔术方法使用

当把一个对象, 进行序列化操作(serialize())的时候, 就会自动先去调用该类中的魔术方法__sleep()。

当对一个对象进行"反序列化"操作的时候, 会先调用该魔术方法__wakeup()。

反序列化时不会自动调用构造方法, 魔术方法__wakeup()就能起到作用(比如连接数据库、选择数据库、设置字符集);

<?php
class Person { //声明一个Person类,包含三个成员属性和一个成员方法
private $name; //人的名子
private $sex; //人的性别
private $age; //人的年龄

function __construct($name="", $sex="", $age="") { //构造方法为成员属性赋初值
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
function say() { //这个人可以说话的方法, 说出自己的成员属性
echo "我的名子叫:".$this->name." 性别:".$this->sex." 我的年龄是:".$this->age."<br>";
}
function __sleep() { //在类中添加此方法,在串行化时自动调用并返回数组
$arr=array("name", "age"); //数组中的成员$name和$age将被串行化,成员$sex则被忽略
return($arr); //返回一个数组
}

function __wakeup() { //在反串行化对象时自动调用该方法,没有参数也没有返回值
$this->age = 40; //在重新组织对象时,为新对象中的$age属性重新赋值
}
}
$person1=new Person("张三", "男", 20); //通过Person类实例化对象,对象引用名为$person1
//把一个对象串行化,返一个字符串,调用了__sleep()方法,忽略没在数组中的属性$sex
$person_string=serialize($person1);
echo $person_string."<br>"; //输出对象串行化的字符串

//反串行化对象,并自动调用了__wakup()方法重新为新对象中的$age属性赋值
$person2=unserialize($person_string); //反串行化对象形成对象$p2重新赋值$age为40
$person2->say(); //调用新对象中say()方法输出的成员中已没有$sex属性了
?>

__invoke(): 将对象当作函数来使用的时候,会自动调用该方法。 invoke: 调用

<?php
class B{
public $p1 = 1;
function __invoke(){
echo "对象当函数使用, 本方法被调用";
}
}
$b1 = new B();
$v1 = $b1(); //此时对象当函数使用
?>
__set_state(), 调用var_export()导出类时, 此静态方法会被调用。
class Test{
public $a;
public function func(){
echo '我只是一个方法';
}
}
$test = new Test();
var_export($test);

会输出:Test::__set_state(array( 'a' => NULL, ))

注意a是NULL, 没有赋值

Tags:

最近发表
标签列表